Merge Join 仅在数据天然有序、双表足够大且需流式输出时比 Hash Join 更高效,其优势在于免建哈希表和随机查找,依靠双指针归并实现恒定内存、零溢出、即时输出。

Merge Join 比 Hash Join 更高效,仅当且仅当数据在连接键上天然有序、两表都足够大、且查询需要流式输出或早期终止时成立。它不是“普遍更快”,而是“在特定条件下免去了 Hash Join 必须做的两件事:建哈希表 + 随机查找”。
Merge Join 真正快在哪?关键看执行路径
Merge Join 的核心是双指针归并:
- 左右两个输入流必须已按连接键升序排列(
Ordered="true") - 每次只比较当前两行的键值,相等就输出,不等就移动较小一方的指针
- 整个过程内存恒定(通常几 KB),无哈希冲突、无桶扩容、不触发磁盘 spill
对比之下,Hash Join:
- 必须先把小表所有连接键读入内存,构建完整哈希表(Build 阶段)
- 再逐行扫描大表去查表(Probe 阶段)
- 若内存不足,会 spill 到磁盘,性能断崖下跌
- 即使只想要前 100 行结果,也得等 Build 完成才吐第一行
所以,Merge Join 的“快”是结构性的,不是算法更聪明,而是绕开了开销大户。
怎么确认 Merge Join 真正在免排序运行?
别只看执行计划顶部写了 Merge Join 就以为赢了。真正要盯的是它的两个直接子节点:
- 使用
EXPLAIN (ANALYZE, BUFFERS) - 找到
<relop logicalop="Merge Join"></relop>节点 - 往下看左右子节点:
- 必须都是
Index Scan或Index Only Scan - 且属性含
Ordered=true
- 必须都是
- 如果任一子节点是
Sort或Seq Scan,说明数据库正在后台偷偷排序——这时 CPU 高、执行慢,根本不是Merge Join的错
还要注意:Rows Removed by Filter 如果远大于 Rows Read(比如读 10 万行,滤掉 9.8 万),说明排序后大量无效数据参与归并,效率早已崩盘。
为什么加了索引,Merge Join 还是没用上?
索引存在 ≠ 能提供有序流。常见断点有:
-
ON a.x = UPPER(b.y):函数导致b.y索引失效,触发隐式Sort - 联合索引
INDEX (status, created_at)无法支撑WHERE created_at > '2025-01-01'的有序输出(未命中左前缀) - 字段类型不一致:如
a.id INT关联b.user_id BIGINT,隐式转换让索引无法用于排序 - 统计信息过期:优化器误判数据分布,放弃走索引扫描
真正难的从来不是建索引,而是让查询条件、字段类型、索引定义、统计信息四者严丝合缝地对齐。漏掉任意一环,Merge Join 就会从加速器变成隐形拖油瓶。
实际调优时,最容易被忽略的点是:你看到的“有序”,未必是优化器认可的有序。它只认执行计划里子节点是否带 Ordered="true",而这个标记只来自索引扫描或显式 ORDER BY ——但后者又会引入额外排序开销。所以,靠索引驱动的有序流,才是 Merge Join 发挥价值的唯一可靠路径。

















