DBMS_JOB系統包是Oracle“任務隊列”子系統的API編程接口。DBMS_JOB包對于任務隊列提供了下面這些功能:提交并且執行一個任務、改變任務的執行參數以及刪除或者臨時掛起任務等。
DBMS_JOB包是由ORACLE_HOME目錄下的rdbms/admin子目錄下的DBMSJOB.SQL和PRVTJOB.PLB 這兩個腳本文件創建的。這兩個文件被CATPROC.SQL腳本文件調用,而CATPROC.SQL這個文件一般是在數據庫創建后立即執行的。腳本為DBMS_JOB包創建了一個公共同義詞,并給該包授予了公共的可執行權限,所以所有的Oracle用戶均可以使用這個包。
下面幾個數據字典視圖是關于任務隊列信息的,主要有DBA_JOBS, USER_JOBS和DBA_JOBS_RUNNING。這些字典視圖是由名為CATJOBQ.SQL的腳本文件創建的。該腳本文件和創建DBMS_JOB包的腳本文件一樣在ORACLE_HOME目錄的rdbms/admin子目錄中,同樣也是由腳本文件CATPROC.SQL調用。
最后,要使任務隊列能正常運行,還必須啟動它自己專有的后臺過程。啟動后臺過程是通過在初始化文件init*.ora(實例不同,初始化文件名也略有不同)中設置初始化參數來進行的。下面就是該參數:
JOB_QUEUE_PROCESSES = n
其中,n可以是0到36之間的任何一個數。除了該參數以外,還有幾個關于任務隊列的初始化參數,本文后面將會對其進行詳細討論。
DBMS_JOB包中包含有許多過程,見表1所示。
表1 DBMS_JOB包
名稱 |
類型 |
描述 |
DBMS_JOB.ISUBMIT |
過程 |
提交一個新任務,用戶指定一個任務號 |
DBMS_JOB.SUBMIT |
過程 |
提交一個新任務,系統指定一個任務號 |
DBMS_JOB.REMOVE |
過程 |
從隊列中刪除一個已經存在的任務 |
DBMS_JOB.CHANGE |
過程 |
更改用戶設定的任務參數 |
DBMS_JOB.WHAT |
過程 |
更改PL/SQL任務定義 |
DBMS_JOB.NEXT_DATE |
過程 |
更改任務下一次運行時間 |
DBMS_JOB.INTERVAL |
過程 |
更改任務運行的時間間隔 |
DBMS_JOB.BROKEN |
過程 |
將任務掛起,不讓其重復運行 |
DBMS_JOB.RUN |
過程 |
在當前會話中立即執行任務 |
DBMS_JOB.USER_EXPORT |
過程 |
創建文字字符串,用于重新創建一個任務 |
三、DBMS_JOB包參數
DBMS_JOB包中所有的過程都有一組相同的公共參數,用于定義任務,任務的運行時間以及任務定時運行的時間間隔。這些公共任務定義參數見表2所示。
表2 DBMS_JOB過程的公共參數
名稱 |
類型 |
注釋 |
Job |
BINARY_INTEGER |
任務的唯一識別號 |
What |
VARCHAR2 |
作為任務執行的PL/SQL代碼 |
Next_date |
VARCHAR2 |
任務下一次運行的時間 |
Interval |
VARCHAR2 |
日期表達式,用來計算下一次任務運行的時間 |
下面我們來詳細討論這些參數的意義及用法。
1、job
參數job是一個整數,用來唯一地標示一個任務。該參數既可由用戶指定也可由系統自動賦予,這完全取決于提交任務時選用了那一個任務提交過程。DBMS_JOB.SUBMIT過程通過獲得序列SYS.JOBSEQ的下一個值來自動賦予一個任務號。該任務號是作為一個OUT參數返回的,所以調用者隨后可以識別出提交的任務。而DBMS_JOB.ISUBMIT過程則由調用者給任務指定一個識別號,這時候,任務號的唯一性就完全取決于調用者了。
除了刪除或者重新提交任務,一般來說任務號是不能改變的。即使當數據庫被導出或者被導入這樣極端的情況,任務號也將被保留下來。所以在執行含有任務的數據的導入/導出操作時很可能會發生任務號沖突的現象。
2、what
what參數是一個可以轉化為合法PL/SQL調用的字符串,該調用將被任務隊列自動執行。在what參數中,如果使用文字字符串,則該字符串必須用單引號括起來。 what參數也可以使用包含我們所需要字符串值的VARCHAR2變量。實際的PL/SQL調用必須用分號隔開。在PL/SQL調用中如果要嵌入文字字符串,則必須使用兩個單引號。
what參數的長度在Oracle7.3中限制在2000個字節以內,在Oracle 8.0以后,擴大到了4000個字節,這對于一般的應用已完全足夠。該參數的值一般情況下都是對一個PL/SQL存儲過程的調用。在實際應用中,盡管可以使用大匿名Pl/SQL塊,但建議大家最好不要這樣使用。還有一個實際經驗就是最好將存儲過程調用封裝在一個匿名塊中,這樣可以避免一些比較莫名錯誤的產生。我來舉一個例子,一般情況下,what參數可以這樣引用:
what =>’my_procedure(parameter1);’ |
但是比較安全的引用,應該這樣寫:
what =>’begin my_procedure(parameter1); end;’ |
任何時候,我們只要通過更改what參數就可以達到更改任務定義的目的。但是有一點需要注意,通過改變what參數來改變任務定義時,用戶當前的會話設置也被記錄下來并成為任務運行環境的一部分。如果當前會話設置和最初提交任務時的會話設置不同,就有可能改變任務的運行行為。意識到這個潛在的副作用是非常重要的,無論何時只要應用到任何DBMS_JOB過程中的what參數時就一定要確保會話設置的正確。
3、next_date
Next_date參數是用來調度任務隊列中該任務下一次運行的時間。這個參數對于DBMS_JOB.SUBMIT和DBMS_JOB.BROKEN這兩個過程確省為系統當前時間,也就是說任務將立即運行。
當將一個任務的next_date參數賦值為null時,則該任務下一次運行的時間將被指定為4000年1月1日,也就是說該任務將永遠不再運行。在大多數情況下,這可能是我們不愿意看到的情形。但是,換一個角度來考慮,如果想在任務隊列中保留該任務而又不想讓其運行,將next_date設置為null卻是一個非常簡單的辦法。
Next_date也可以設置為過去的一個時間。這里要注意,系統任務的執行順序是根據它們下一次的執行時間來確定的,于是將next_date參數設置回去就可以達到將該任務排在任務隊列前面的目的。這在任務隊列進程不能跟上將要執行的任務并且一個特定的任務需要盡快執行時是非常有用的。
4、Interval
Internal參數是一個表示Oracle合法日期表達式的字符串。這個日期字符串的值在每次任務被執行時算出,算出的日期表達式有兩種可能,要么是未來的一個時間要么就是null。這里要強調一點:很多開發者都沒有意識到next_date是在一個任務開始時算出的,而不是在任務成功完成時算出的。
當任務成功完成時,系統通過更新任務隊列目錄表將前面算出的next_date值置為下一次任務要運行的時間。當由interval表達式算出next_date是null時,任務自動從任務隊列中移出,不會再繼續執行。因此,如果傳遞一個null值給interval參數,則該任務僅僅執行一次。
通過給interval參數賦各種不同的值,可以設計出復雜運行時間計劃的任務。本文后面的“任務間隔和日期算法”將對interval表達式進行詳細討論,并給出一個實際有用interval表達式的例子。
四、任務隊列架構和運行環境
任務隊列在Oracle系統中其實是一個子系統,它具有自己特定的后臺過程和目錄表。該子系統設計的目的是為了能不在用戶干預下自動運行PL/SQL過程。
1、任務隊列后臺過程
任務隊列(SNP)后臺過程隨著Oracle實例的啟動而同時啟動。在文章前面已經談到初始化文件init.ora中的參數JOB_QUEUE_PROCESSES,用來設置有幾個隊列過程。這里設置了幾個過程,系統中就會有幾個SNP過程被啟動。JOB_QUEUE_PROCESSES這個參數,可以是0到36中的任何一個數,也就是說對于每個Oracle實例最多可以有36個SNP過程,也可以不支持隊列過程(=0)。在大多數操作系統中,SNP三個字母常作為過程名的一部分出現。如,在unix系統中,如果該Oracle實例名為ora8,有三個任務隊列過程,則這三個任務隊列過程名稱為:
ora_ora8_snp0
ora_ora8_snp1
ora_ora8_snp2 |
SNP后臺過程和其他的Oracle后臺過程的一個重要區別就是殺掉一個SNP過程不會影響到Oracle實例。當一個任務隊列過程失控或者消耗太多的資源時,就可以將其殺掉,當然這種情況不是經常遇到的。當一個SNP過程被殺掉或者失敗時,Oracle就自動啟動一個新的SNP過程來代替它。
2、有關任務隊列的初始化參數
初始化文件init.ora中的幾個參數控制著任務隊列后臺的運行,下面我們將對其進行詳細討論。
(1)、JOB_QUEUE_INTERVAL
任務隊列過程定期喚醒并檢查任務隊列目錄表是否有任務需要執行。參數JOB_QUEUE_INTERVAL決定SNP過程兩次檢查目錄表之間“休眠”多長時間(單位為秒)。間隔設的太小會造成由于SNP過程不斷檢查目錄表而導致不必要的系統吞吐量。相反如果間隔設得太大,SNP過程在特定的時間沒有被喚醒,那個時間的任務就不會能被運行。最佳的時間間隔設置要綜合考慮系統環境中不同的任務,60秒的確省設置可以滿足大多數的應用。
(2)、JOB_QUEUE_KEEP_CONNECTIONS
除了前面介紹的JOB_QUEUE_PROCESS和JOB_QUEUE_INTERVAL兩個參數以外,影響SNP后臺過程行為的第三個參數是JOB_QUEUE_KEEP_CONNECTIONS。當該參數為TRUE時,SNP過程在兩個任務的運行期間(也就是休眠期間),仍然和Oracle保持開放的連接。相反,如果為FALSE時,SNP過程將和數據庫斷開連接,當喚醒時刻到來時又重新連接并檢查任務隊列。
選擇這兩種方法中的那一種,主要是考慮任務隊列的有效性和數據庫關閉方法。長期保持連接的效率比較高,但任務隊列會受到正常關閉數據庫的影響。這是因為任務隊列過程對于服務器管理器看來和一個普通用戶的過程沒有什么不同,而正常的關閉數據庫需要讓所有的用戶都斷開連接。而斷開連接和重新連接又給數據庫增加了負荷,但是可定期地使數據庫沒有可連接SNP過程,也就可以使數據庫正常關閉。對于有很多任務或者是任務重復執行的時間間隔較短(一個小時或者更少)的環境,一般將JOB_QUEUE_KEEP_CONNECTIOONS設置為TRUE,并修改關閉數據庫的腳本為立即關閉。對于嚴格要求采用正常方式關閉的數據庫或者是任務較少,重復間隔較長的環境,一般將該參數設置為FALSE。最好,要提醒一句,SNP過程僅在沒有任何任務運行時才斷開,這種情況下,那些需要比較長時間運行的任務SNP將在它們的生命周期內一致保持開放的連接,這就延遲了正常關閉數據庫的時間。
3、建立運行環境
當SNP過程喚醒時,它首先查看任務隊列目錄中所有的任務是否當前的時間超過了下一次運行的日期時間。SNP檢測到需要該時間立即執行的任務后,這些任務按照下一次執行日期的順序依次執行。當SNP過程開始執行一個任務時,其過程如下:
- 以任務所有者的用戶名開始一個新的數據庫會話。
- 當任務第一次提交或是最后一次被修改時,更改會話NLS設置和目前就緒的任務相匹配。
- 通過interval日期表達式和系統時間,計算下一次執行時間。
- 執行任務定義的PL/SQL
- 如果運行成功,任務的下一次執行日期(next_date)被更新,否則,失敗計數加1。
- 經過JOB_QUEUS_INTERVAL秒后,又到了另一個任務的運行時間,重復上面的過程。
在前兩步中,SNP過程創建了一個模仿用戶運行任務定義的PL/SQL的會話環境。然而,這個模仿的運行環境并不是和用戶實際會話環境完全一樣,需要注意以下兩點:第一,在任務提交時任何可用的非確省角色都將在任務運行環境中不可用。因此,那些想從非確省角色中取得權限的任務不能提交,用戶確省角色的修改可以通過在任務未來運行期間動態修改來完成。第二,任何任務定義本身或者過程執行中需要的數據庫聯接都必須完全滿足遠程的用戶名和密碼。SNP過程不能在沒有顯式指明口令的情況下初始化一個遠程會話。顯然,SNP過程不能假定將本地用戶的口令作為遠程運行環境會話設置的一部分。
提交的任務如果運行失敗會怎么樣呢?當任務運行失敗時,SNP過程在1分鐘后將再次試圖運行該任務。如果這次運行又失敗了,下一次嘗試將在2分鐘后進行,再下一次在4分鐘以后。任務隊列每次加倍重試間隔直到它超過了正常的運行間隔。在連續16次失敗后,任務就被標記為中斷的(broken),如果沒有用戶干預,任務隊列將不再重復執行。
五、任務隊列字典表和視圖
任務隊列中的任務信息可以通過表3所示的幾個字典視圖來查看,這些視圖是由CATJOBQ.sql腳本創建的。表4和5是各個視圖每個字段的含義。
表3. 任務隊列中關于任務的數據字典視圖
視圖名 |
描述 |
DBA_JOBS |
本數據庫中定義到任務隊列中的任務 |
DBA_JOBS_RUNNING |
目前正在運行的任務 |
USER_JOBS |
當前用戶擁有的任務 |
表4. DBA_JOBS 和 USER_JOBS.字典視圖的字段含義
字段(列) |
類型 |
描述 |
JOB |
NUMBER |
任務的唯一標示號 |
LOG_USER |
VARCHAR2(30) |
提交任務的用戶 |
PRIV_USER |
VARCHAR2(30) |
賦予任務權限的用戶 |
SCHEMA_USER |
VARCHAR2(30) |
對任務作語法分析的用戶模式 |
LAST_DATE |
DATE |
最后一次成功運行任務的時間 |
LAST_SEC |
VARCHAR2(8) |
如HH24:MM:SS格式的last_date日期的小時,分鐘和秒 |
THIS_DATE |
DATE |
正在運行任務的開始時間,如果沒有運行任務則為null |
THIS_SEC |
VARCHAR2(8) |
如HH24:MM:SS格式的this_date日期的小時,分鐘和秒 |
NEXT_DATE |
DATE |
下一次定時運行任務的時間 |
NEXT_SEC |
VARCHAR2(8) |
如HH24:MM:SS格式的next_date日期的小時,分鐘和秒 |
TOTAL_TIME |
NUMBER |
該任務運行所需要的總時間,單位為秒 |
BROKEN |
VARCHAR2(1) |
標志參數,Y標示任務中斷,以后不會運行 |
INTERVAL |
VARCHAR2(200) |
用于計算下一運行時間的表達式 |
FAILURES |
NUMBER |
任務運行連續沒有成功的次數 |
WHAT |
VARCHAR2(2000) |
執行任務的PL/SQL塊 |
CURRENT_SESSION_LABEL |
RAW MLSLABEL |
該任務的信任Oracle會話符 |
CLEARANCE_HI |
RAW MLSLABEL |
該任務可信任的Oracle最大間隙 |
CLEARANCE_LO |
RAW MLSLABEL |
該任務可信任的Oracle最小間隙 |
NLS_ENV |
VARCHAR2(2000) |
任務運行的NLS會話設置 |
MISC_ENV |
RAW(32) |
任務運行的其他一些會話參數 |
表 5. 視圖DBA_JOBS_RUNNING的字段含義
列 |
數據類型 |
描述 |
SID |
NUMBER |
目前正在運行任務的會話ID |
JOB |
NUMBER |
任務的唯一標示符 |
FAILURES |
NUMBER |
連續不成功執行的累計次數 |
LAST_DATE |
DATE |
最后一次成功執行的日期 |
LAST_SEC |
VARCHAR2(8) |
如HH24:MM:SS格式的last_date日期的小時,分鐘和秒 |
THIS_DATE |
DATE |
目前正在運行任務的開始日期 |
THIS_SEC |
VARCHAR2(8) |
如HH24:MM:SS格式的this_date日期的小時,分鐘和秒 |
六、任務重復運行間隔和間隔設計算法
任務重復運行的時間間隔取決于interval參數中設置的日期表達式。下面就來詳細談談該如何設置interval參數才能準確滿足我們的任務需求。一般來講,對于一個任務的定時執行,有三種定時要求。
- 在一個特定的時間間隔后,重復運行該任務。
- 在特定的日期和時間運行任務。
- 任務成功完成后,下一次執行應該在一個特定的時間間隔之后。
第一種調度任務需求的日期算法比較簡單,即'SYSDATE+n',這里n是一個以天為單位的時間間隔。表6給出了一些這種時間間隔設置的例子。
表6 一些簡單的interval參數設置例子
描述 |
Interval參數值 |
每天運行一次 |
'SYSDATE + 1' |
每小時運行一次 |
'SYSDATE + 1/24' |
每10分鐘運行一次 |
'SYSDATE + 10/(60*24)' |
每30秒運行一次 |
'SYSDATE + 30/(60*24*60)' |
每隔一星期運行一次 |
'SYSDATE + 7' |
不再運行該任務并刪除它 |
NULL |
表6所示的任務間隔表達式不能保證任務的下一次運行時間在一個特定的日期或者時間,僅僅能夠指定一個任務兩次運行之間的時間間隔。例如,如果一個任務第一次運行是在凌晨12點,interval指定為'SYSDATE + 1',則該任務將被計劃在第二天的凌晨12點執行。但是,如果某用戶在下午4點手工(DBMS_JOB.RUN)執行了該任務,那么該任務將被重新定時到第二天的下午4點。還有一個可能的原因是如果數據庫關閉或者說任務隊列非常的忙以至于任務不能在計劃的那個時間點準時執行。在這種情況下,任務將試圖盡快運行,也就是說只要數據庫一打開或者是任務隊列不忙就開始執行,但是這時,運行時間已經從原來的提交時間漂移到了后來真正的運行時間。這種下一次運行時間的不斷“漂移”是采用簡單時間間隔表達式的典型特征。
第二種調度任務需求相對于第一種就需要更復雜的時間間隔(interval)表達式,表7是一些要求在特定的時間運行任務的interval設置例子。
表 7. 定時到特定日期或時間的任務例子
描述 |
INTERVAL參數值 |
每天午夜12點 |
'TRUNC(SYSDATE + 1)' |
每天早上8點30分 |
'TRUNC(SYSDATE + 1) + (8*60+30)/(24*60)' |
每星期二中午12點 |
'NEXT_DAY(TRUNC(SYSDATE ), ''TUESDAY'' ) + 12/24' |
每個月第一天的午夜12點 |
'TRUNC(LAST_DAY(SYSDATE ) + 1)' |
每個季度最后一天的晚上11點 |
'TRUNC(ADD_MONTHS(SYSDATE + 2/24, 3 ), 'Q' ) -1/24' |
每星期六和日早上6點10分 |
'TRUNC(LEAST(NEXT_DAY(SYSDATE, ''SATURDAY"), NEXT_DAY(SYSDATE, "SUNDAY"))) + (6×60+10)/(24×60)' |
第三種調度任務需求無論通過怎樣設置interval日期表達式也不能滿足要求。這時因為一個任務的下一次運行時間在任務開始時才計算,而在此時是不知道任務在何時結束的。遇到這種情況怎么辦呢?當然辦法肯定是有的,我們可以通過為任務隊列寫過程的辦法來實現。這里我只是簡單介紹以下,可以在前一個任務隊列執行的過程中,取得任務完成的系統時間,然后加上指定的時間間隔,拿這個時間來控制下一個要執行的任務。這里有一個前提條件,就是目前運行的任務本身必須要嚴格遵守自己的時間計劃。
結論
Oracle中的定時任務是在Oracle系統中是一個非常重要的子系統,運用得當,可以極大的提高我們的系統運行和維護能力。而Oracle數據復制的延遲事務隊列管理完全是基于Oracle的隊列任務,對其的深刻理解有助于我們更好地管理數據復制。