Merge Join必须看到Ordered="true",因为它依赖双指针归并算法,要求左右输入流严格按联接字段升序排列;若任一输入实际无序,SQL Server会自动插入Sort操作符补救,导致CPU飙升,故需逐层验证子节点是否为索引扫描且Ordered="true"。

Merge Join 为什么必须看到 Ordered="true"
Merge Join 不是“边扫边连”,而是双指针归并:它假设左右两个输入流已经按联接字段升序排列,然后各取一行比大小,小的往前走,相等就输出。一旦任一输入流实际无序,算法立刻失效——所以 SQL Server 不会冒险执行,而是悄悄在 Merge Join 前插一个Sort 操作符来补救。
这个补救动作不报错、不警告,但会吃掉大量 CPU,尤其当 ActualRows 达几十万时,Sort 节点常成执行计划里最亮的黄色叹号。
验证是否真有序,不能只看执行计划写了 Merge Join,得往下钻两层:
– 找到 <relop logicalop="Merge Join"></relop> 节点
– 检查它的两个子节点是否都是 Index Scan 或 Clustered Index Seek
– 关键:这两个子节点属性里必须含 Ordered="true"
建了索引也不等于物理有序
索引存在 ≠ 扫描时有序。常见破坏点包括:-
ON a.x = b.y,但a表上只有CREATE INDEX ix_a_xy ON a(x, y)—— 这个索引能用,但若查询中还用了y做过滤,优化器可能放弃按x的顺序扫描 -
b表上索引是CREATE INDEX ix_b_yz ON b(y, z),而联接条件只涉及y—— 理论上可行,但若统计信息过期或z列更新频繁导致页分裂,物理存储可能已乱序 - 联接字段类型不一致,比如
a.id是INT,b.a_id是BIGINT,触发隐式转换,索引无法按原顺序被扫描
最稳的索引写法是单列、与联接字段完全一致:CREATE INDEX ix_a_x ON a(x)、CREATE INDEX ix_b_y ON b(y)。
统计信息过期会让 Ordered="true" 变成假命题
即使索引完好、查询干净,过期的统计信息也会让优化器误判数据分布,从而错误地认为“走索引扫出来大概率无序”,进而主动加Sort 或退化为 Hash Join。
检查方法:
– 运行 DBCC SHOW_STATISTICS('table_name', 'index_name')
– 看 modification_counter 是否超过表总行数的 20%
– 若是,立刻执行 UPDATE STATISTICS table_name WITH FULLSCAN
同时对比执行计划中每个子节点的 EstimatedRows 和 ActualRows:差 5 倍以上基本可断定统计不准。
SSIS 中的 Merge Join 完全不认数据库排序
SSIS 的Merge Join 转换和 SQL Server 引擎无关,它只认两个元数据标记:IsSorted = true 和列级 SortKeyPosition。哪怕你在源 SQL 里写了 ORDER BY x,SSIS 也不会自动识别——因为 SQL Server 排序规则和 SSIS 要求的 Windows 排序规则对字符串处理不一致。
安全做法只有一条:
– 在 Merge Join 前加一个 Sort 转换(它会自动设好 IsSorted 和 SortKeyPosition)
– 如果坚持用 SQL 排序,必须先 CONVERT(NVARCHAR, x) 再 ORDER BY,否则中文、特殊字符顺序可能错位
手设 IsSorted = true 是高危操作:数据流实际无序时,合并结果不可信,且没有任何运行时提示。

















