执行计划显示Nested Loops但实际很慢,先查内表连接字段是否有索引;若无索引,每轮循环触发全表扫描,总成本≈外表行数×内表行数,导致性能断崖式下降。

执行计划里看到Nested Loops,但实际很慢,先查内表有没有索引
常见错误现象是:执行计划显示Nested Loops,但查询耗时飙升,尤其外层返回几百行、内表却要全表扫描。这是因为优化器误判了内表访问成本——它以为连接列有索引,而实际上没有。
使用场景:小驱动表 + 大被驱动表,且被驱动表连接字段**必须有可用索引**(最好是选择性高的非空索引)。否则每轮循环都触发一次全表扫描,总成本 ≈ 外表行数 × 内表行数。
- 检查方式:
EXPLAIN (ANALYZE, BUFFERS)(PostgreSQL)或SET STATISTICS XML ON(SQL Server),看内表节点是否带Index Scan或INDEX SEEK - 参数差异:MySQL 的
join_buffer_size对 NLJ 无效;Oracle/SQL Server 的索引深度、统计信息新鲜度直接影响 NLJ 是否被选中 - 容易踩的坑:外表加了
LIMIT但优化器没下推,仍按全量估算;或连接字段存在隐式类型转换(如INTvsVARCHAR),导致索引失效
Hash Join出现Warning: No join predicate,说明ON条件漏写了
这是 Hash Join 最典型的崩溃信号。一旦执行计划里出现这个警告,实际执行就是笛卡尔积,性能断崖式下跌——哪怕两表各只有 1 万行,结果也是 1 亿行。
使用场景:仅适用于等值连接(=),且两表都较大、无有效索引、或连接字段无序。优化器会自动把估算更小的一侧作为 build table(哈希构建侧),和语法上的 LEFT/RIGHT 无关。
- 内存影响极大:PostgreSQL 受
work_mem限制;MySQL 8.0+ 看join_buffer_size;SQL Server 用max server memory分配 hash 内存。超出则写磁盘,I/O 暴增 - 性能陷阱:被驱动表含
TEXT/BLOB字段时,MySQL 可能物化整行进哈希表,内存占用远超预期 - 验证方法:SQL Server 悬停 JOIN 算子看
Estimated Row Count和Actual Row Count是否严重偏离;差距大就该更新统计信息:UPDATE STATISTICS
为什么LEFT JOIN有时比INNER JOIN快
这不是玄学,核心在于优化器对驱动表的选择被语法“锁死”了。LEFT JOIN 强制左表为驱动表,而 INNER JOIN 允许优化器自由交换顺序——有时它选错了,让大表当了外表。
典型场景:左表 users(1 万行)、右表 orders(500 万行),orders.user_id 无索引。LEFT JOIN 被迫用 users 驱动,走 NLJ;INNER JOIN 却可能误判选 orders 为外表,导致中间结果膨胀。
- 关键线索:看执行计划里哪个表标有
Outer References(SQL Server)或出现在Nested Loop上方(PostgreSQL)——那就是实际驱动表 - NULL 传播也起作用:如果
orders.user_id大量为 NULL,INNER JOIN 过滤后结果集变小,但优化器基于过期统计仍按大结果预估资源,反而拖慢 - 别盲目改写:先确认执行计划,再决定是否加
STRAIGHT_JOIN(MySQL)或/*+ LEADING() */(Oracle)固定顺序
强制指定JOIN算法前,先确认优化器为什么没选它
直接加 USE_HASH 或 USE_NL Hint 很容易翻车。比如在 MySQL 中删掉一个低效索引后 Hash Join 自动生效,比硬编码 Hint 更稳。
真正需要干预的情况极少:通常是统计信息严重滞后、或连接字段存在无法消除的隐式转换,导致优化器成本估算彻底失真。
- 优先动作永远是:更新统计信息(
ANALYZE TABLE/UPDATE STATISTICS/DBMS_STATS.GATHER_TABLE_STATS) - 再检查连接字段类型是否严格一致(
INTvsTINYINT都可能触发转换) - 最后才考虑 Hint:Oracle 用
/*+ USE_NL(t1 t2) */,SQL Server 用OPTION (HASH JOIN),但注意这些会绕过优化器所有成本判断
最常被忽略的点是:Hash Join 的 build table 不是你写的 LEFT 表,而是优化器估算更小的那个——哪怕你用 LEFT JOIN,它照样可能把右表拿去建哈希表。


















