首页 > 数据库 > Oracle > Oracle如何实现批量 DML 的手动并行
大批量 UPDATE、DELETE 的执行时间通常与行数成正比,串行跑到几个小时是常有的事。提高这类语句的速度,并行hint(`/*+ PARALLEL */``)对查询成熟,但对 DML,数据库默认只并行读、不并行写,真正要并行执行 DML 还得开启会话级并行 DML,粒度、资源都不可控。
DBMS_PARALLEL_EXECUTE
该包 EXECUTE 权限授予了 PUBLIC,但并行执行依赖 Scheduler 作业,用户需要 CREATE JOB 权限。
DBMS_PARALLEL_EXECUTE .CREATE_TASK (
task_name IN VARCHAR2 ,
comment IN VARCHAR2 DEFAULT NULL ) ;
DBMS_PARALLEL_EXECUTE .CREATE_CHUNKS_BY_ROWID (
task_name IN VARCHAR2 ,
table_owner IN VARCHAR2 ,
table_name IN VARCHAR2 ,
by_row IN BOOLEAN , -- TRUE: chunk_size 按行数,FALSE: 按块数
chunk_size IN NUMBER ) ;
DBMS_PARALLEL_EXECUTE .RUN_TASK (
task_name IN VARCHAR2 ,
sql_stmt IN CLOB ,
language_flag IN NUMBER , -- 通常传 DBMS_SQL.NATIVE
parallel_level IN NUMBER DEFAULT 0 ,
job_class IN VARCHAR2 DEFAULT 'DEFAULT_JOB_CLASS' ) ;
DBMS_PARALLEL_EXECUTE .DROP_TASK (
task_name IN VARCHAR2 ) ;
除 ROWID 外,还有两个切分手段:
CREATE_CHUNKS_BY_NUMBER_COL
按表的数字列区间切(适合有连续编号列的表),
CREATE_CHUNKS_BY_SQL
完全用自定义 SQL 定义片边界(适合按业务维度切)。
任务与分片的状态分别记录在两张视图里:
任务状态(USER_PARALLEL_EXECUTE_TASKS) :
| 状态 | 含义 |
|---|---|
| CREATED | 任务已创建 |
| CHUNKING / CHUNKING_FAILED | 切分中 / 切分失败 |
| CHUNKED / NO_CHUNKS | 切分完成 / 无分片 |
| PROCESSING | 执行中 |
| FINISHED / FINISHED_WITH_ERROR | 全部成功 / 部分分片失败 |
| CRASHED | 任务崩溃 |
分片状态(USER_PARALLEL_EXECUTE_CHUNKS) :
| 状态 | 含义 |
|---|---|
| UNASSIGNED | 未分配 |
| ASSIGNED | 已分配给作业 |
| PROCESSED / PROCESSED_WITH_ERROR | 处理成功 / 处理失败 |
两个关键行为特征:其一,
RUN_TASK
是阻塞调用,任务全部结束后才返回,执行期间可以从另一会话查分片状态观察进度;其二,
每个分片处理完即提交
,因此中途失败的任务再次执行
RUN_TASK
时,只会处理未完成的分片,天然的断点续跑。
1. 授权(DBA)
SQL > grant create job to test ;
Grant succeeded.
2. 准备数据(TEST 用户)
SQL > create table t_pe (id number , pad varchar2 ( 100 ) , flag number default 0 ) ;
Table created.
SQL > begin
2 for i in 1 .. 200000 loop
3 insert into t_pe values (i , rpad ( 'x' , 100 , 'x' ) , 0 ) ;
4 end loop ;
5 commit ;
6 end ;
7 /
PL/SQL procedure successfully completed.
3. 创建任务并切分
SQL > begin
2 dbms_parallel_execute .create_task ( 'T_PE_UPDATE' , '测试任务' ) ;
3 dbms_parallel_execute .create_chunks_by_rowid (
4 task_name => 'T_PE_UPDATE' ,
5 table_owner => user ,
6 table_name => 'T_PE' ,
7 by_row => true ,
8 chunk_size => 50000 ) ;
9 end ;
10 /
PL/SQL procedure successfully completed.
SQL > select chunk_id , status , start_rowid , end_rowid
2 from user_parallel_execute_chunks
3 where task_name = 'T_PE_UPDATE'
4 order by chunk_id ;
CHUNK_ID STATUS START_ROWID END_ROWID
---------- ---------- ------------------ ------------------
1 UNASSIGNED AAAWiXAAEAAAAkSAAA AAAWiXAAEAAAAtPCcg
2 UNASSIGNED AAAWiXAAEAAAAtPDAAA AAAWiXAAEAAAAyfCcg
3 UNASSIGNED AAAWiXAAEAAAAyfDAAA AAAWiXAAEAAAA8PCcg
4 UNASSIGNED AAAWiXAAEAAAA8PDAAA AAAWiXAAEAAABDPCcg
20 万行按每片 5 万行切成了 4 片,每片记录了 ROWID 范围边界。
4. 并行执行
SQL > declare
2 l_sql clob : =
3 'update t_pe set flag = 1
4 where rowid between :start_id and :end_id
5 and flag = 0' ;
6 begin
7 dbms_parallel_execute .run_task (
8 task_name => 'T_PE_UPDATE' ,
9 sql_stmt => l_sql ,
10 language_flag => dbms_sql .native ,
11 parallel_level => 4 ) ;
12 end ;
13 /
PL/SQL procedure successfully completed.
注意 DML 中的占位符:按文档要求必须命名为
:start_id
与
:end_id
——ROWID 切分时二者的类型是 ROWID,数字列切分时是 NUMBER。
另外语句中的
flag = 0
条件让重跑时天然跳过已处理的行,是配合断点续跑的惯用写法。
5. 检查结果
SQL > select status , count ( * ) from user_parallel_execute_chunks
2 where task_name = 'T_PE_UPDATE'
3 group by status ;
STATUS COUNT ( * )
----------- ----------
PROCESSED 4
SQL > select task_name , status from user_parallel_execute_tasks
2 where task_name = 'T_PE_UPDATE' ;
TASK_NAME STATUS
------------- --------------------
T_PE_UPDATE FINISHED
SQL > select count ( * ) from t_pe where flag = 1 ;
COUNT ( * )
----------
200000
四个分片全部 PROCESSED,任务状态 FINISHED,20 万行更新完毕。若有分片失败,状态为 PROCESSED_WITH_ERROR,
ERROR_MESSAGE
列保留报错文本,修正后重新
RUN_TASK
即可只补跑失败的分片。
6. 清理任务
SQL > exec dbms_parallel_execute .drop_task ( 'T_PE_UPDATE' )
PL/SQL procedure successfully completed.
每片处理完立即提交,整个任务 不具备全局原子性 ——中断后是"做了一部分"的状态,业务上要么接受(配合幂等的 WHERE 条件重跑),要么就不适合用这个工具;
包内子程序(少数查询函数除外)调用时会提交,不要把 CREATE_TASK 等调用嵌在自己的业务事务中间;
parallel_level
是同时运行的作业数,不是分片数;它决定了同时打开的并行 DML 资源消耗,按表上的锁、undo 和回滚压力确定,一般从 4、8 起步观察;
ROWID 切分按行数估算边界,片与片之间行数不完全均匀(块内行数不同所致),不影响正确性,只影响负载均衡;
目标表正在被其他会话修改时,分片内的 DML 一样要等行锁,切分解决不了锁等待,只解决单进程写入速度;
任务名全局唯一,重跑前先确认旧任务状态并 DROP_TASK 清理。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70013625/viewspace-3184249/ ,如需转载,请注明出处,否则将追究法律责任。