Nested Loop连接在Oracle 19c分区表上常因驱动表非真小、被驱动表索引未对齐分区键或统计信息陈旧而退化为全分区扫描,性能远差于Hash Join;关键在于驱动表输出行数须稳定小于5000且索引需覆盖分区键与连接键。
nested loop连接在oracle 19c分区表上容易因分区裁剪失效或索引未对齐而退化成全分区扫描,性能可能比hash join差一个数量级——关键不在“用不用nl”,而在“驱动表是否真正小”和“被驱动表的分区键+连接键是否共用索引”。
为什么分区表上的Nested Loop常变慢
分区表本身不自动提升NL效率;反而容易踩三个坑:
- 驱动表走分区扫描(比如
PARTITION RANGE ALL),实际返回几万行,导致被驱动表被反复访问几万次 - 被驱动表的连接列(如
order_id)没建本地索引,或建了但没包含分区键,导致每次NL内层都扫整个分区甚至全表 - 统计信息陈旧,CBO误判驱动表行数,选了NL但实际驱动结果集远超10,000行
必须检查的索引结构:本地索引要覆盖分区键+连接键
被驱动表若按sale_date范围分区,又常按customer_id连接,则索引不能只建(customer_id)——它无法利用分区裁剪。正确做法是:
- 建本地前缀索引:
CREATE INDEX idx_sales_cust ON sales(customer_id, sale_date) LOCAL;(注意sale_date必须是分区键,且放第二位) - 避免非前缀索引:
CREATE INDEX idx_sales_cust_noprefix ON sales(customer_id) LOCAL;——这种索引在NL中仍需跨所有分区查找,失去分区优势 - 验证执行计划中被驱动表访问路径是否为
INDEX RANGE SCAN而非TABLE ACCESS FULL或PARTITION LIST ALL
强制USE_NL时,务必配LEADING和INDEX Hint
单写/*+ USE_NL(t1 t2) */风险极高:优化器可能把大分区表当驱动表。必须显式控制顺序和访问路径:
- 指定驱动表优先级:
/*+ LEADING(small_dim) USE_NL(sales) INDEX(sales idx_sales_cust) */ - 如果
small_dim本身也分区,确保其过滤条件能触发PARTITION RANGE SINGLE,否则LEADING无效 - 禁用自适应计划干扰:
/*+ OPT_PARAM('_optimizer_adaptive_plans' 'false') */(Oracle 19c默认开启,可能runtime切走Hash Join)
真正决定NL快不快的,是驱动表输出行数是否稳定小于5,000
别信“小表”这种模糊说法——要看执行计划里Rows列的实际预估值,且该值必须来自准确统计信息:
- 对驱动表执行:
EXEC DBMS_STATS.GATHER_TABLE_STATS('SCHEMA','SMALL_DIM', GRANULARITY=>'ALL', METHOD_OPT=>'FOR ALL COLUMNS SIZE AUTO'); - 检查是否用了动态采样:
SELECT * FROM V$SQL_PLAN WHERE SQL_ID = 'xxx' AND OTHER_XML LIKE '%dynamic_sampling%';——有则说明统计信息缺失,NL不可靠 - 如果驱动表过滤后仍超10,000行,NL基本不是最优选;此时应接受Hash Join,哪怕加
USE_NLhint也会被CBO忽略或降级为BNL(Block Nested Loop),反而更慢
最易被忽略的一点:分区表上的NL性能拐点不在数据量,而在驱动表结果集的**稳定性**。一旦业务逻辑导致某天驱动表返回行数突增10倍,NL就会雪崩——所以监控PLAN_HASH_VALUE变化比调优单条SQL更重要。


















