一、簡介
ORACLE11g R2版本的新特性之一就是引進了DBMS_PARALLEL_EXECUTE包,使用DBMS_PARALLEL_EXECUTE包批量並行遞增式的更新表。
更多ORACLE11g新特性請參考:http://www.cnblogs.com/oracle-dba/articles/3632223.html
基本原理:
(1) 把數據集分割成小的塊(chunk),可基於rowid進行分塊,也可根據指定范圍的行數(rows)進行分塊。
(2) 在每一個塊上以並行的方式應用update語句,在每個塊執行完成后,立即提交,原理是通過調用JOB進行並發操作。
好處在於:
(1) 在執行update操作時,僅僅鎖住一個chunk而非鎖住整個表;
(2) 因為對每個chunk 執行完畢就提交,所以當update操作失敗后,之前變更的並不會回滾;
(3) 減小回滾空間(undo)的使用;
(4) 提高性能,可根據服務器的性能設置chunk_size與parallel_level的大小。
備注:parallel_level取決於CPU的個數,以及parallel_threads_per_cpu。
DBMS_PARALLEL_EXECUTE 使用三種將一個表的數據分割成多個chunk的方法:
(1) CREATE_CHUNKS_BY_NUMBER_COL : 通過指定的字段來切割表
(2) CREATE_CHUNKS_BY_ROWID : 通過ROWID來切割表,本文只介紹通過BY ROWID 進行分割
(4) CREATE_CHUNKS_BY_SQL : 通過用戶提供的sql語句來切割表
二、實踐操作
DBMS_PARALLEL_EXECUTE這個包操作起來比較簡單,大體步驟為:
(1)創建任務task,即調用create_task()過程;
(2)創建分塊規則,即調用create_chunk_by_rowid()或者create_chunk_by_number_col()等過程;
(3)寫動態update的sql語句,復雜和簡單的均可,可以通過dbms_output.put_line()來驗證動態sql的正確性;
(4)運行task任務,即調用run_task()過程;
(5)監控task任務,可通過視圖user_parallel_execute_tasks和user_parallel_execute_chunks來監控task狀態;
(6)task成功運行完畢后,將任務刪除,即調用drop_task()過程。
2.1 准備工作
給用戶授權
調用dbms_parallel_execute包時,需要有執行create job的權限,因此需要給用於賦予create job權限。
grant create job to username;
也可把grant execute on dbms_scheduler to username;賦予某用戶。
2.2 寫代碼
實踐操作代碼,有改動,僅做參考。
CREATE OR REPLACE PROCEDURE Pro_Parallel_Exec_Update(i_Taskid IN VARCHAR2, --任務ID 傳入是序列 para_task_seq.nextval i_Inputdate IN DATE, --日期 i_Tabname IN VARCHAR2, --更新表名 i_Column IN VARCHAR2, --更新列名 i_Tasktp IN VARCHAR2 DEFAULT 'BY ROWID' -- 任務類型 未啟用的,可設置不能類型的 update -- 如: 如不同類型的分塊方法 by rowid , by number_col ,by sql ) IS
/*******************************************************************
PROC_NAME : 並行更新過程,本例采用BY ROWID 方法
EDIT_NAME : zzd @2014-03-26
REQUIREMENT :
1) ORACLE 11g R2 版本
2) DBMS_PARALLEL_EXECUTE
3) OWN CREATE JOB PRIVILEGE eg: GRANT EXECUTE ON DBMS_SCHEDULER TO USER ;
4) SET JOB_QUEUE_PROCESSES
REFERENCE:
(1) USER_PARALLEL_EXECUTE_CHUNKS
(2) USER_PARALLEL_EXECUTE_TASKS
(3) DBMS_PARALLEL_EXECUTE.DROP_TASK(TASK_NAME);
ATTACH :
(1) 分塊范圍 Chunk_Size 和並行度 Paralle_Level 可以根據服務器性能自行設置
(2) DBMS_PARALLEL_EXECUTE 包三種 (BY ROWID, BY NUMBER_COL ,BY SQL ) 分塊方法,原理差不多
(3) 更多說明參考PLSQL Packages and Types Reference_11R2.pdf
********************************************************************/
--- 任務名常量
v_Taskname VARCHAR2(20) := 'UPDATE_TASK';
--- schema常量
v_Schema CONSTANT VARCHAR2(20) := 'USER';
--- rowid范圍常量
v_Cksize CONSTANT INT := 8000;
--- 並行度大小
v_Plevel CONSTANT INT := 10;
--- 日期變量
v_Inputdate VARCHAR2(64);
--- 臨時動態sql 變量
v_Sql_Stmt VARCHAR2(4000);
--- 臨時動態insert_sql 變量
v_Sql_Insert VARCHAR2(4000);
--- 控制嘗試運行次數
v_Trynum NUMBER;
--- 返回運行狀態
v_Status NUMBER;
BEGIN
v_Trynum := 0;
--- 日期轉換
v_Inputdate := 'TO_DATE(''' || To_Char(i_Inputdate, 'YYYY-MM-DD') ||
''',''' || 'YYYY-MM-DD' || ''')';
--- 寫日志開始更新
Dbms_Output.Put_Line('開始運行 ' || ' USER = ' || v_Schema ||
' Table_Name= ' || i_Tabname ||
' Update_Column = ' || i_Column || ' 任務類型為: ' ||
i_Tasktp);
--- 注意這里只能用實體表作為臨時表,此處稱“中間表”否則在創建task 這步會把臨時表的數據情況,事務性和會話性臨時表均不可以
--- 清空 中間表數據
EXECUTE IMMEDIATE ' Truncate TABLE User_Table_Temp '; -- 注這個是個實體中間表
--- 動態 insert sql 語句
v_Sql_Insert := ' INSERT INTO User_Table_Temp(Table_Name, Status)
SELECT a.Table_Name, a.Status
FROM User_Tables a
WHERE a.Table_Name LIKE ' || '''' ||
' USER_NAME% ' || '''';
--- 執行動態sql
EXECUTE IMMEDIATE v_Sql_Insert;
--- 測試動態sql
Dbms_Output.Put_Line(v_Sql_Insert);
--- 創建任務
v_Taskname := v_Taskname || ' _ ' || i_Taskid;
Dbms_Parallel_Execute.Create_Task(v_Taskname);
--- 使用 ROWID 進行范圍分組
Dbms_Parallel_Execute.Create_Chunks_By_Rowid(Task_Name => v_Taskname,
Table_Owner => Upper(v_Schema),
Table_Name => Upper(i_Tabname),
By_Row => TRUE,
Chunk_Size => v_Cksize);
--- 動態update的sql 腳本
v_Sql_Stmt := ' UPDATE /*+ ROWID (dda) */ ' || Upper(i_Tabname) || ' b
SET b.' || i_Column;
v_Sql_Stmt := v_Sql_Stmt || ' = (SELECT a.TABLE_NAME|| A.STATUS
FROM User_Table_Temp a
WHERE b. Object_Name' ||
' = a.TABLE_NAME
AND b.Created = ' || v_Inputdate || ') ';
v_Sql_Stmt := v_Sql_Stmt || '
WHERE EXISTS (SELECT 1
FROM User_Table_Temp a
WHERE b.Object_Name ' ||
' = a.TABLE_NAME
AND b.Created = ' || v_Inputdate || ')';
v_Sql_Stmt := v_Sql_Stmt || Chr(10) ||
'AND ROWID BETWEEN :Start_Id AND :End_Id ';
--- 測試動態SQL
Dbms_Output.Put_Line(v_Sql_Stmt);
--- 執行並行更新
Dbms_Parallel_Execute.Run_Task(Task_Name => v_Taskname,
Sql_Stmt => v_Sql_Stmt,
Language_Flag => Dbms_Sql.Native,
Parallel_Level => v_Plevel);
--- 更新結束
Dbms_Output.Put_Line('完成運行 ' || ' USER = ' || v_Schema ||
' Table_Name= ' || i_Tabname ||
' Update_Column = ' || i_Column || ' 任務類型為: ' ||
i_Tasktp);
--- 如果 task任務出差,則恢復它,並重新執行,如此進行嘗試兩次
v_Status := Dbms_Parallel_Execute.Task_Status(v_Taskname);
WHILE (v_Trynum < 2 AND v_Status != Dbms_Parallel_Execute.Finished) LOOP
v_Trynum := v_Trynum + 1;
--- 恢復任務,嘗試繼續執行
Dbms_Parallel_Execute.Resume_Task(v_Taskname);
--- 獲取當前任務狀態
v_Status := Dbms_Parallel_Execute.Task_Status(v_Taskname);
END LOOP;
-- 更新完畢后,刪除此任務
Dbms_Parallel_Execute.Drop_Task(v_Taskname);
END Pro_Parallel_Exec_Update;
三、相關視圖
(1)USER_PARALLEL_EXECUTE_CHUNKS
(2)USER_PARALLEL_EXECUTE_TASKS