嵌套循环变慢主因是驱动表选择错误、连接列缺索引、统计信息陈旧或谓词未下推;需通过AWR定位真实执行计划,检查ACCESS/FILTER谓词及ROWS_PROBED,优化应先验证驱动合理性、建合适组合索引并更新准确统计信息。
为什么嵌套循环(NESTED LOOPS)在Oracle里突然变慢?
不是所有 nested loops 都有问题,但当你在awr报告里看到某条sql的 elapsed time 大部分耗在 cpu 或 db file sequential read,且执行计划中出现多层 nested loops 套嵌(尤其外层驱动表未走索引、内层无有效过滤),基本可以判定是访问路径失控。常见诱因包括:驱动表选择错误、连接列缺少索引、统计信息陈旧、或谓词未下推到内表。
从AWR抓出问题SQL和真实执行计划
别只看SQL_ID摘要——AWR默认只存 PLAN_HASH_VALUE,而同一SQL_ID可能因绑定变量不同走不同计划。必须用以下方式定位真实执行路径:
- 查
DBA_HIST_SQLSTAT找高ELAPSED_TIME_TOTAL的SQL_ID,再关联DBA_HIST_SQL_PLAN拉取对应PLAN_HASH_VALUE的完整执行计划 - 重点看
ACCESS_PREDICATES和FILTER_PREDICATES列:如果内表连接条件出现在FILTER而非ACCESS,说明没走索引查找,而是先全表扫描再过滤 - 检查
ROWS_PROBED(来自DBA_HIST_SQL_PLAN_STATISTICS):若内表被反复探测上万次,而每次只返回1–2行,就是典型的“小结果集驱动大表”反模式
优化嵌套循环的三个实操动作
不盲目加索引,先确认驱动关系是否合理:
- 用
/*+ USE_NL(t1 t2) */强制NL后,对比/*+ USE_HASH(t1 t2) */的逻辑读差异——如果HASH明显更低,说明NL本身就不适合当前数据分布 - 给内表连接列建组合索引时,把驱动表的等值条件列放在前面,例如:内表是
orders,连接条件为orders.cust_id = customers.id,则索引应为CREATE INDEX idx_orders_custid ON orders(cust_id, order_date)(而非只建cust_id单列) - 更新统计信息时,对驱动表用
ESTIMATE_PERCENT => DBMS_STATS.AUTO_SAMPLE_SIZE,但对内表务必加method_opt => 'FOR ALL COLUMNS SIZE AUTO',否则直方图缺失会导致CBO误判内表选择率
容易被忽略的隐性坑
即使执行计划显示走了索引,也可能因底层机制失效:
-
NESTED LOOPS不支持并行执行——如果内表很大,就算加了PARALLELhint,Oracle也只会并行扫描驱动表,内表仍是串行逐次访问 - 当驱动表有
ROWNUM 或 <code>FIRST_ROWS(n)优化器模式时,CBO会倾向选NL,但这只对前几行快,总量不变甚至更差 - 函数索引(如
UPPER(name))无法用于NL的ACCESS,除非连接条件完全匹配该函数表达式,否则退化为FILTER
真正卡点往往不在语法或索引,而在驱动表的数据倾斜程度——比如一个 status = 'ACTIVE' 条件实际返回95%的记录,却成了NL的外层,这时再好的内表索引也救不回IO雪崩。










