oracle原生并行dml对非分区表无效,因其缺乏天然数据边界,优化器会静默忽略parallel提示;必须用dbms_parallel_execute配合rowid分块实现并行更新。
不能直接用 run_task 执行任意存储过程调用,必须封装成带 rowid 范围参数的可复用过程,并在动态 pl/sql 块中通过绑定变量传入 chunk 边界。
为什么 UPDATE /*+ PARALLEL */ 对非分区表无效?
Oracle 的原生并行 DML(如 UPDATE /*+ PARALLEL(t,4) */)要求目标表必须是分区表,否则优化器会静默忽略提示——查 v$px_session 看不到 PX slave,EXPLAIN PLAN 里也无 UPDATE (PARALLEL) 操作符。这不是 bug,是设计限制:非分区表缺乏天然的、可独立锁定与提交的数据边界。
常见错误现象:
- 执行后耗时和串行一样,但没报错
-
SELECT * FROM v$px_session返回空结果 - 事务锁住整张表,而非局部行段
CREATE_CHUNKS_BY_ROWID 是最稳妥的切块方式
它按物理存储位置(ROWID)把表切成连续行段,不依赖业务字段、不扫描全表统计、执行计划稳定,适合绝大多数场景。
使用前提与限制:
- 表不能是索引组织表(IOT)或临时表,否则报错
ORA-29400: data cartridge error CHUNKING BY ROWID NOT SUPPORTED FOR THIS TABLE TYPE -
CHUNK_SIZE建议设为 5000–50000 行:太小导致任务调度开销占比高;太大则单 chunk 失败时重试成本高 - 执行前确认
job_queue_processes > 0(常见值设为 1000),否则任务创建成功但永远不触发执行 - 切块后立刻查
dba_parallel_execute_chunks,确认STATUS = 'ASSIGNED'且CHUNK_ID连续无空缺
RUN_TASK 最容易踩的权限和语法坑
RUN_TASK 不是执行任意 PL/SQL 块,而是以每个 chunk 为单位,拼接并执行一条动态 SQL —— 这条 SQL 必须能独立完成该 chunk 的全部逻辑,且不能含 DDL、事务控制语句(COMMIT/ROLLBACK)。
典型错误写法及修正:
-
COMMIT会报ORA-14551: cannot perform a DML operation inside a query→ 改为让每个 chunk 自动 commit(RUN_TASK内部已隐式 commit) - SQL 字符串中直接拼接变量 → 必须用绑定变量,或通过
DBMS_PARALLEL_EXECUTE.GET_CHUNK_ROWID获取当前 chunk 范围 - 安全写法是封装一个带
ROWID范围参数的过程,再在sql_stmt中调用:'BEGIN serial(:start_rid, :end_rid); END;' - 需要
CREATE JOB和EXECUTE ON DBMS_SQL权限,普通开发账号常缺前者,得找 DBA 授权
并发执行存储过程的关键点
核心不是“并行跑存储过程”,而是“把存储过程改造成接受 ROWID 范围输入,并确保其内部只操作该范围内的数据”。否则会出现重复处理、漏处理或 ORA-01410 错误。
实操建议:
- 过程体里必须用
WHERE rowid BETWEEN :start_rid AND :end_rid限定范围,不能用业务字段(如 ID)做条件,除非你用了CREATE_CHUNKS_BY_NUMBER_COL且字段严格单调递增无空洞 - 避免在过程中显式
COMMIT或SAVEPOINT,RUN_TASK会在每个 chunk 完成后自动 commit - 如果过程内有
INSERT INTO ... SELECT,注意目标表也要有对应约束或唯一性校验,防止 chunk 间冲突 - 失败 chunk 可用
RESUME_TASK续跑,但前提是过程本身幂等——同一 chunk 多次执行不应破坏数据一致性
真正难的不是写几行 DBMS_PARALLEL_EXECUTE 调用,而是把原有逻辑拆解成可分片、可重入、无状态的单元。ROWID 范围看似简单,但一旦表发生迁移、分区交换或在线重定义,ROWID 就失效了——这点容易被忽略。











