左连接比内连接慢是因为必须保留左表全部行,导致优化器难以提前过滤右表数据,常触发Table Scan而非Index Seek,I/O开销更大;关键看执行计划中Estimated Number of Rows、Estimated I/O Cost和Actual Number of Rows三项。

左连接为什么比内连接慢:先看执行计划里的扫描类型
因为左连接必须保留左表全部行,优化器往往无法像内连接那样提前过滤右表数据,导致更多行被读取和关联。在执行计划中,你常会看到左连接触发 Table Scan 或 Clustered Index Scan,而等价的内连接可能走 Index Seek —— 扫描成本直接差一个数量级。
执行计划里哪些字段暴露了性能差异
重点盯紧三个地方:Estimated Number of Rows(预估行数)、Estimated I/O Cost(I/O开销)、Actual Number of Rows(实际返回行数)。左连接下,即使右表没匹配项,左表每行仍要参与一次关联尝试,所以 Actual Number of Rows 往往远大于内连接对应算子的值;若右表缺少合适索引,Estimated I/O Cost 会飙升。
- 右表连接列没建索引 → 强制全表扫描,
Estimated I/O Cost翻倍甚至更高 - 左表有大量 NULL 值且参与连接 → 优化器可能放弃使用索引,退化为扫描
- 连接条件含函数或类型转换(如
CONVERT(VARCHAR, id))→ 索引失效,Seek变Scan
如何让左连接接近内连接的性能
核心是让右表能用上 Index Seek,同时减少左表无效行参与关联。不是加索引就完事,得看怎么加、加在哪。
- 右表连接字段必须有单列索引或作为复合索引的最左前缀,例如连接条件是
t2.user_id = t1.id,就在t2.user_id上建索引 - 避免在左表连接列上写
WHERE条件后还做左连接;先把左表过滤好再连,比如把LEFT JOIN ... WHERE t1.status = 'active'改成子查询或 CTE 先筛出活跃用户 - 确认统计信息最新:过期的统计信息会让优化器误判行数,选择错误的连接算法(如选
Hash Match而非Nested Loops)
一个典型陷阱:外连接 + WHERE 条件等于变相内连接
很多人写 LEFT JOIN t2 ON t1.id = t2.t1_id WHERE t2.status = 'done',以为还是左连接语义——其实 WHERE 会过滤掉所有 t2.status 为 NULL 的行,结果和 INNER JOIN 完全一致,但执行计划仍按左连接逻辑生成,白白多扫一遍左表。
这种写法不仅没拿到左连接的好处,还承担了它的开销。真要保留左表所有行,WHERE 条件必须挪到 ON 子句里:LEFT JOIN t2 ON t1.id = t2.t1_id AND t2.status = 'done'。否则优化器看不到语义意图,也就没法重写或优化。
左连接的“保留左表”特性看似简单,但一旦混入过滤逻辑,执行路径就很容易失控。别只盯着 SQL 写法是否合规,得看执行计划里每一行的 Actual Number of Rows 和 EstimateRows 是否合理对得上。


















