资讯动态

Oracle并行执行实战:从开启方式到调优避坑全解析

发布时间:2026/10/14 9:28:42 来源:尧图企业网站定制
1. 并行执行到底解决了什么问题很多人第一次接触 Oracle 并行是因为一条统计 SQL 跑了四十分钟还没出结果而隔壁同事同样的表、同样的条件几分钟就出数了。差别往往不在 SQL 写法而在于他悄悄开了并行。但并行不是万能开关开错了反而会把整个库拖慢。我见过最典型的一次事故某业务系统在白天高峰期对一张核心订单表开了PARALLEL 16结果一条查询把 CPU 全部吃满其他会话全部排队最后只能紧急 kill 会话。所以先把定位说清楚并行执行Parallel Execution的本质是把一个串行的执行计划拆成多个子任务分发给多个并行执行进程Parallel Execution Server同时干活最后由协调进程Query Coordinator汇总结果。它解决的是单进程处理大数据量时 CPU、IO 资源利用不足的问题而不是SQL 写得烂的问题。一条逻辑本身有问题的 SQL开了并行只会更快地把错误结果算出来。它适合的场景很明确大表全表扫描、大表之间的 Hash Join、大表排序聚合数据仓库、报表库、批量跑批这类对响应时间敏感、对并发不敏感的场景单条 SQL 消耗的 CPU 时间远超墙上时间wall-clock time说明资源没吃满它不适合的场景同样明确OLTP 高并发短事务一条 SQL 毫秒级返回开并行纯属浪费已经跑满 CPU 的系统再开并行只会加剧争抢小表、走索引的精确查询并行反而增加协调开销理解了这个边界后面所有的参数、视图、调优才有意义。下面我按怎么开、怎么查、怎么调、怎么避坑的顺序把我在实际项目里踩过的和总结的东西完整讲一遍。2. 开启并行的几种方式与优先级Oracle 里开启并行不是一个开关而是分布在多个层级上的。很多人搞不清我明明在 SQL 里写了 hint为什么没生效根本原因就是没搞清楚优先级。这一节把开启方式和优先级彻底讲透。2.1 从实例级到语句级的四层控制并行的控制粒度从粗到细大致分四层优先级从低到高层级控制方式作用范围典型用途实例级PARALLEL_DEGREE_POLICY、PARALLEL_MAX_SERVERS等参数整个实例全局资源上限对象级建表/索引时的PARALLEL n子句单张表或索引让对象默认带并行度会话级ALTER SESSION FORCE PARALLEL QUERY当前会话临时给会话强制并行语句级/* PARALLEL(t, n) */hint单条 SQL精确控制某条语句优先级规则很简单语句级 hint 会话级 对象级 实例默认。但这里有个坑对象级的并行度会被继承。如果你建表时写了PARALLEL 8那么之后所有查这张表的语句只要没被更高优先级覆盖都会默认用 8 并行。这就是为什么有些库莫名其妙到处都在并行——建表的人图省事加了并行后面没人清理。2.2 对象级并行的设置与清理建表时直接指定CREATE TABLE t_order ( order_id NUMBER, cust_id NUMBER, amount NUMBER, create_dt DATE ) PARALLEL 8;已经建好的表可以改ALTER TABLE t_order PARALLEL 8; -- 恢复默认通常是 1即不并行 ALTER TABLE t_order NOPARALLEL;索引同理ALTER INDEX idx_order_cust PARALLEL 4;。这里我要强调一个实操经验生产环境建表时除非这张表明确是给报表/跑批用的否则不要随手加 PARALLEL。我接手过一个库几百张表全是PARALLEL 4结果一个普通的联机查询触发了并行协调进程和 4 个并行进程一起抢资源响应时间反而从 20ms 涨到 300ms。清理的时候写个脚本批量NOPARALLEL就行-- 生成批量清理语句 SELECT ALTER TABLE || owner || . || table_name || NOPARALLEL; FROM dba_tables WHERE owner APP_USER AND degree 1;2.3 会话级强制并行有时候你不想改对象定义只想在当前会话里让所有查询都并行ALTER SESSION FORCE PARALLEL QUERY PARALLEL 4;注意FORCE这个词——它会覆盖对象级的设置强制按你给的并行度来。跑完记得关掉否则这个会话后续所有查询都带并行ALTER SESSION DISABLE PARALLEL QUERY;这个方式特别适合临时跑一个大报表连上去、FORCE、跑 SQL、DISABLE、断开。干净利落不留痕迹。2.4 语句级 hint 的精确控制最常用的还是 hint因为它只影响一条语句SELECT /* PARALLEL(o 8) */ cust_id, SUM(amount) FROM t_order o WHERE create_dt DATE 2024-01-01 GROUP BY cust_id;这里PARALLEL(o 8)里的o是表别名8 是并行度。也可以不写别名直接/* PARALLEL(8) */表示对语句里所有相关对象都用 8。还有一种写法/* PARALLEL(o) */不带数字表示用对象默认并行度或系统自动计算。提示hint 里的表别名必须和 FROM 子句里的别名完全一致写错了 hint 会被静默忽略不报错。这是排查hint 不生效时第一个要检查的点。2.5 为什么我的 hint 没生效这是被问得最多的问题。除了别名写错还有几个常见原因语句里有阻止并行的操作比如调用了某些不允许并行的 PL/SQL 函数、使用了MODEL子句、或者涉及远程表dblink的某些场景。并行度被实例参数限制PARALLEL_MAX_SERVERS太小或者PARALLEL_DEGREE_LIMIT卡住了上限。资源管理器Resource Manager限制如果配了资源消费组并行度可能被组策略压制。对象被标记为 NOPARALLEL 且用了 FORCE 之外的写法某些情况下对象级设置会覆盖语句级需要显式 FORCE。排查的时候先看执行计划里有没有PX相关的操作PX COORDINATOR、PX SEND、PX RECEIVE等没有就说明并行根本没起来。3. 并行度到底该设多少并行度Degree of ParallelismDOP设多少是并行调优里最核心也最容易拍脑袋的问题。我见过有人无脑设 32也见过有人保守设 2。其实它是有计算逻辑的不是玄学。3.1 并行度的自动计算逻辑当你不指定具体数字只写/* PARALLEL(t) */或者依赖自动并行时Oracle 会根据几个因素算出一个默认并行度CPU 核数实例可用的 CPU 数量这是基础。PARALLEL_THREADS_PER_CPU每个 CPU 对应的并行线程数默认通常是 2。对象大小表/索引占用的块数太小的话自动并行会直接放弃。PARALLEL_DEGREE_POLICY设为AUTO时才会启用自动并行度计算。默认并行度大致等于CPU 核数 × PARALLEL_THREADS_PER_CPU。比如 16 核的机器默认并行度可能算到 32。这个数字在专用报表库上没问题但在混合负载的库上就偏大了。3.2 手动设定并行度的经验公式我的经验是并行度不要超过这条 SQL 能真正用上的资源数。一个粗略但好用的公式并行度 ≈ min(可用 CPU 核数, 表所在磁盘能提供的有效 IO 带宽对应的并发数)对于 CPU 密集型的操作Hash Join、排序、聚合并行度可以接近 CPU 核数对于 IO 密集型的操作全表扫描并行度受限于存储的 IOPS 和带宽设太高反而因为 IO 争抢导致效率下降。实际项目里我常用的做法是报表库、专用跑批库并行度设为 CPU 核数的 50%~75%混合负载库并行度控制在 4~8且只在夜间跑批时开单条 SQL 临时调优从 4 开始试逐步加到 8、16观察执行时间拐点3.3 用实测数据找拐点并行度不是越大越好存在明显的边际递减。我做过一组实测同一张大表做聚合不同并行度下的耗时大致如下数据为示意实际因环境而异并行度执行耗时CPU 使用率说明1320s8%串行资源闲置495s35%明显加速858s62%接近线性1642s88%收益递减3240s100%几乎无提升资源打满可以看到从 16 到 32 几乎没有收益但 CPU 从 88% 冲到 100%留给其他会话的空间被榨干了。拐点通常出现在 CPU 使用率达到 80% 左右的位置再往上加并行度就是纯浪费还会伤害并发。3.4 并行度与并发的取舍这是最容易被忽视的一点。并行度是单条 SQL 用多少资源并发是同时有多少条 SQL。两者相乘才是系统总负载。如果系统要同时支撑 10 条并行查询每条并行度 16那就是 160 个并行进程远超PARALLEL_MAX_SERVERS的话后面的查询就得排队等并行进程。所以设并行度之前先问自己这条 SQL 跑的时候系统上还有多少别的活如果是夜间独占跑批可以放开如果是白天和联机业务混跑就得克制。我一般的做法是给跑批任务单独规划时间窗口窗口内可以放心用高并行度窗口外一律 NOPARALLEL。4. 并行执行计划怎么读开了并行之后执行计划会变得比串行复杂得多多出一堆PX开头的操作。看不懂这些操作就没法判断并行到底有没有按你预期的方式跑。4.1 PX 操作符的含义并行计划里最常见的几个操作符PX COORDINATOR协调进程负责分发任务和汇总结果它本身不干重活。PX SEND / PX RECEIVE并行进程之间传递数据。PX SEND是发送端PX RECEIVE是接收端中间的数据分发方式广播、哈希、范围决定了性能。PX BLOCK ITERATOR把表扫描任务切块分给各个并行进程。PX SELECTOR某些版本里用于筛选并行块。一个典型的并行全表扫描计划长这样| Id | Operation | Name | TQ |IN-OUT| PQ Distrib | |----|----------------------|---------|--------|------|------------| | 0 | SELECT STATEMENT | | | | | | 1 | PX COORDINATOR | | | | | | 2 | PX SEND QC (RANDOM)| :TQ10000| Q1,00 | P-S | QC (RAND) | | 3 | PX BLOCK ITERATOR | | Q1,00 | PCWC | | | 4 | TABLE ACCESS FULL| T_ORDER | Q1,00 | PCWP | |看这个计划重点看TQ列表队列标识并行进程组和IN-OUT列数据流向。P-S表示并行到串行PCWC表示并行子进程到协调进程PCWP表示并行子进程到并行子进程。4.2 数据分发方式对性能的影响PQ Distrib列显示的是数据分发方式这是并行 Join 性能的关键BROADCAST把小表广播到每个并行进程适合小表驱动大表。HASH按 Join 键做哈希分发适合大表对大表。RANGE按范围分发常用于并行排序。PARTITION按分区分发适合分区表。如果发现大表对大表用了 BROADCAST那基本就是灾难——每个并行进程都要接收一份完整的大表数据网络和内存都会爆。这种情况通常是因为统计信息不准优化器误判了表的大小。解决办法是重新收集统计信息或者用 hint 强制/* PQ_DISTRIBUTE(t1 HASH HASH) */。4.3 用 DBMS_XPLAN 查看真实并行计划普通EXPLAIN PLAN看到的计划可能和实际执行的不一样尤其是并行度。要看真实计划用SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(sql_id, NULL, ALLSTATS LAST));ALLSTATS LAST会带上实际执行的行数、耗时、内存等信息能看出每个并行进程实际干了多少活。如果发现某个PX SEND的实际行数远超预估说明统计信息有问题或者数据倾斜严重。注意查看DISPLAY_CURSOR需要 SQL 已经执行过且还在共享池里。如果被刷出去了就查不到了所以调优时最好及时查。5. 并行相关的关键参数与视图光会开并行还不够得知道系统层面的参数怎么配以及出了问题去哪儿查。这一节把常用的参数和视图梳理清楚。5.1 必须了解的并行参数参数含义建议PARALLEL_MAX_SERVERS实例允许的最大并行进程数按 CPU 核数 × 2~4 设置别设太大PARALLEL_MIN_SERVERS启动时预分配的并行进程数跑批库可设联机库设 0PARALLEL_DEGREE_POLICY并行度策略混合负载用 MANUAL专用报表库可 AUTOPARALLEL_DEGREE_LIMIT自动并行度的上限防止自动算出过大的并行度PARALLEL_THREADS_PER_CPU每 CPU 的并行线程数默认 2一般不用改PARALLEL_ADAPTIVE_MULTI_USER多用户自适应降并行混合负载建议 TRUEPARALLEL_MAX_SERVERS是最需要关注的。设太小并行查询排队等进程设太大一旦并发上来进程数爆炸内存和 CPU 都扛不住。我的经验值是CPU 核数 × 2到CPU 核数 × 4之间具体看负载类型。5.2 监控并行执行的动态视图排查并行问题主要看这几个视图-- 当前正在运行的并行查询 SELECT sid, serial#, degree, req_degree, sql_id, status FROM v$px_session WHERE qcsid IS NOT NULL; -- 并行进程的等待事件 SELECT s.sid, s.event, s.wait_class, s.seconds_in_wait FROM v$session s WHERE s.sid IN (SELECT sid FROM v$px_session); -- 并行进程使用情况汇总 SELECT * FROM v$px_process_sysstat;v$px_session里degree是实际并行度req_degree是请求的并行度。如果degree明显小于req_degree说明并行进程不够用被降级了。这时候要么调大PARALLEL_MAX_SERVERS要么降低请求的并行度。5.3 并行进程不够用的典型症状并行进程不足时症状很有特点SQL 执行时间忽长忽短执行计划里并行度时高时低v$px_session里能看到大量会话处于PX Deq: Execution Msg之类的等待。这时候查一下SELECT statistic, value FROM v$px_process_sysstat WHERE statistic IN (Servers Busy, Servers Idle, Servers Highwater, Servers Started);如果Servers Highwater已经接近PARALLEL_MAX_SERVERS那就是进程池被打满了。解决办法有两个一是错峰跑批二是适当调大上限但调大之前一定要确认 CPU 和内存扛得住。6. 那些年我踩过的并行坑参数和语法都是死的真正让人头疼的是实际跑起来之后的各种意外。这一节我把踩过的坑按排查链路完整还原希望能帮你少走弯路。6.1 并行把联机业务拖垮的完整排查有一次夜间跑批我开了一条并行度 16 的大查询结果第二天早上业务反馈系统卡。排查过程是这样的第一步看 AWR 报告发现早上 8 点到 9 点 CPU 使用率异常高PX相关等待明显。第二步查v$px_session历史通过 ASH发现那条跑批 SQL 居然跑到了早上还没结束并行进程一直占着。第三步看执行计划发现它做了一次全表扫描加 Hash Join但表比预估的大得多统计信息是两周前的。根因很清楚统计信息过期导致优化器低估了数据量选了不合适的并行计划加上跑批没设时间窗口直接撞上了早高峰。修复方案是跑批前先收集统计信息给跑批任务加资源消费组限制并且设置PARALLEL_DEGREE_LIMIT兜底。这个坑的教训是并行查询一旦跑起来不会因为你想让它停就停它会一直占着并行进程直到结束。所以跑批任务一定要有超时和资源隔离机制。6.2 统计信息不准导致的并行度失控统计信息对并行的影响比想象中大。优化器算并行度时会参考表的块数、行数。如果统计信息显示表只有 1000 行实际有 1 亿行优化器可能直接放弃并行觉得表太小不值得或者选一个完全错误的 Join 方式。我遇到过一次一张分区表新加的分区没收集统计信息优化器认为整个表很小结果并行查询走了 Nest Loop慢得离谱。解决办法是确保统计信息及时收集BEGIN DBMS_STATS.GATHER_TABLE_STATS( ownname APP_USER, tabname T_ORDER, degree 8, cascade TRUE, estimate_percent DBMS_STATS.AUTO_SAMPLE_SIZE ); END; /注意degree 8这里是收集统计信息本身的并行度和查询并行是两回事但同样能加速统计收集。6.3 并行 DML 与直接路径插入的坑并行不仅能用于查询还能用于 DML/* PARALLEL */加在 INSERT/UPDATE/DELETE 上。但并行 DML 有几个硬性前提必须显式ALTER SESSION ENABLE PARALLEL DML;或加 hint表不能有触发器某些版本限制并行 UPDATE/DELETE 需要表有合适的并行度设置并行 DML 常配合直接路径插入/* APPEND */使用能大幅加速大批量数据加载ALTER SESSION ENABLE PARALLEL DML; INSERT /* APPEND PARALLEL(t 8) */ INTO t_order_his SELECT /* PARALLEL(o 8) */ * FROM t_order o WHERE create_dt DATE 2024-01-01;但直接路径插入有个大坑它会绕过 buffer cache直接写数据文件且插入的数据在提交前对其他会话不可见。如果插入过程中失败数据不会自动回滚需要手动清理。所以大批量插入前一定要确认事务边界别在长事务里用。6.4 并行度被资源管理器悄悄压制如果数据库配了资源管理器Resource Manager并行度可能被消费组的策略限制。症状是hint 里明明写了 16执行计划里却只有 4。排查方法是查当前会话的资源消费组SELECT sid, resource_consumer_group FROM v$session WHERE sid SYS_CONTEXT(USERENV, SID);然后看这个组的并行度限制。这种情况在托管环境或者多租户环境里特别常见因为平台方为了公平会给每个租户的并行度设上限。遇到这种情况改 hint 没用得找 DBA 调整资源计划。7. 让并行真正跑得快的几个实操技巧前面讲了原理和坑最后这部分是我在实际调优中总结出来的、能直接用的技巧。每一条都对应一个具体的场景。7.1 用并行提示组合控制 Join 分发当并行 Join 出现数据倾斜或者分发方式不对时用PQ_DISTRIBUTE强制指定SELECT /* PARALLEL(t1 8) PARALLEL(t2 8) PQ_DISTRIBUTE(t2 HASH HASH) */ t1.cust_id, SUM(t2.amount) FROM t_cust t1, t_order t2 WHERE t1.cust_id t2.cust_id GROUP BY t1.cust_id;PQ_DISTRIBUTE(t2 HASH HASH)表示 t2 用哈希方式分发到各个并行进程。如果 t2 是小表可以改成BROADCAST NONE把小表广播出去避免大表被重分发。7.2 分区表配合并行效果翻倍分区表和并行是天生一对。如果表按时间分区并行扫描可以按分区切分每个并行进程处理一个或几个分区IO 也更均衡SELECT /* PARALLEL(t 8) */ COUNT(*) FROM t_order t WHERE create_dt DATE 2024-01-01;配合分区裁剪Partition Pruning只扫描相关分区再叠加并行大表查询能快一个数量级。但要注意分区数量最好和并行度匹配分区太多会导致并行进程频繁切换反而有开销。7.3 并行度与 PGA 内存的关系并行执行会消耗更多 PGA 内存因为每个并行进程都要有自己的排序区、哈希区。如果并行度设得很高而PGA_AGGREGATE_TARGET不够就会出现大量direct path write temp或者pga memory operation等待性能反而下降。我的经验是并行度翻倍PGA 需求大致也翻倍。开高并行之前先确认 PGA 有足够余量。可以查SELECT name, value/1024/1024 AS mb FROM v$pgastat WHERE name IN (aggregate PGA target parameter, aggregate PGA auto target, total PGA allocated);如果total PGA allocated已经接近 target就别再往上加并行度了。7.4 用 SQL 监控实时观察并行进度长跑批任务最怕不知道跑到哪了。用V$SQL_MONITOR可以实时看并行执行进度SELECT sql_id, status, px_servers_allocated, px_servers_requested, elapsed_time/1000000 AS elapsed_sec, cpu_time/1000000 AS cpu_sec FROM v$sql_monitor WHERE status EXECUTING ORDER BY elapsed_time DESC;px_servers_allocated和px_servers_requested的对比能直接告诉你并行进程够不够。如果 allocated 远小于 requested说明进程池紧张这条 SQL 正在降级运行。7.5 并行执行的收尾清理并行查询结束后并行进程不会立刻释放而是回到进程池待命。如果系统平时不需要并行可以定期清理空闲进程或者把PARALLEL_MIN_SERVERS设为 0让系统按需创建。另外跑完大批量并行任务后建议做一次统计信息收集因为并行 DML 会改变数据分布旧统计信息可能已经不准了。我在实际项目里的习惯是每次大规模跑批结束后自动触发一次相关表的统计信息收集并把这次跑批的并行度、耗时、CPU 使用率记录到一张监控表里。时间长了就能看出规律——哪张表在什么并行度下效率最高下次直接复用不用再试。这个习惯帮我省了大量重复调优的时间也让跑批任务的稳定性提升了不少。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑