MySQL join on 条件顺序同样影响SQL效率

都2026年了,SQL优化居然还有是改写SQL文本的条件前后顺序,前面有案例<MySQL 多表关连时优化器缺陷出现的错误Join order>记录过多表join时优化器的评估缺陷,最近又看到了MySQL在两表关加的on的多个等值条件时,条件的书写顺序同样会导致性能问题。简单记录。

案例1 on 的条件位置不同

T1表大约3万行,T2表大约100万行(T2.f2字段90%是null),T1.f1,T2.f1,T2.f2都有索引,下面两条sql的结果集是一模一样的,差异点只有一个:T2.f2 IS NULL 放在了哪里。为什么第一条sql要3秒,第二条sql只有1毫秒?

SELECT * FROM T1 LEFT JOIN T2 ON T1.f1 = T2.f1 AND T2.f2 IS NULL WHERE T1.f1 = '2875205';

SELECT * FROM T1 LEFT JOIN T2 ON T1.f1 = T2.f1 WHERE T1.f1 = '2875205' AND T2.f2 IS NULL;

这个现象非常典型,本质是 LEFT JOIN + 条件位置不同,导致 MySQL 优化器选择了完全不同的执行路径。你这两条 SQL 逻辑等价(在这个场景下),但执行方式不等价,所以性能差很多。

sql1:

执行计划第一行完全没问题,T1.f1='2875205' 直接 const/ref 定位,第二行优化器选择了 idx_f2(单列索引),问题在这。

因为join条件里有T2.f2 is null,所以选择了T2.f2的索引idx_f2,idx_f2 选择性极差,扫描行数是1+1*90w,就是说T1每匹配一行,都需要扫描T2的90%的行,扫描的是大量 NULL 记录,虽然被驱动表走了索引,但又不完全走,索引没完全覆盖,约等于BNL算法被驱动表全表扫描,所以流程实际上是:

idx_f2 扫一堆 NULL

回表

判断 f1 是否等于

ON 里的条件是 JOIN 条件,MySQL 必须保证 LEFT JOIN 语义,不能随便把条件下推、重排,优化器认为“f2 IS NULL 是 JOIN 条件的一部分,那我用它的索引吧。即使它过滤能力很差,也只能这么选。

sql2:

T2.f2 IS NULL 写在 WHERE 里,T1.f1 = T2.f1选择了T2.f1的索引idx_f1,扫描行数是1+n,LEFT JOIN 被“降级”为 INNER JOIN,不再保留 T2 不匹配的行,LEFT JOIN 的“补 NULL 语义”消失,优化器 可以大胆重排条件。优化器改用 idx_f1(高选择性),这是 “先少后筛”,成本低得多。

两条 SQL 的“真实执行差异”

慢 SQL(ON 里)
T2.idx_f2  ↓大量 NULL 行  ↓回表  ↓再比对 f1
快 SQL(WHERE 里)
T1.idx_f1 → const  ↓T2.idx_f1 精确查  ↓再判断 f2 IS NULL

差距不在 rows 显示值,而在“扫描路径”

rows估算值idx_f2真实扫描成本 被严重低估,NULL 值分布对优化器并不“友好”。

优化方法

语义允许 → 改 INNER JOIN(最优),如快的SQL

必须 LEFT JOIN → 联合索引是唯一正解

CREATE INDEX idx_t2_f1_f2 ON T2(f1, f2);

案例2 on 的条件前后顺序不同

这个案例发生在TDSQL for MySQL中,当on 后多个关连条件,仅调整了条件的顺序,SQL响应时间从300s到27s.

当存在联合索引 (cod_acct_no, cod_dep_no) 时:

a.cod_acct_no = b.cod_acct_noAND a.cod_dep_no  = b.cod_dep_no

a.cod_dep_no  = b.cod_dep_noAND a.cod_acct_no = b.cod_acct_no

更容易命中联合索引的最优使用路径

👉 因此更快

没有拿到完整的执行计划,仅做猜测。

SQL 的逻辑结果当然一样,但 MySQL 在 JOIN 时并不是完全无序解析条件的。MySQL 在 JOIN 时是“边解析边评估”的,MySQL 并不会总是把 ON 里的等值条件“完美重排”,优化器评估是有顺序的。所以“把高选择性的列写前面”,或分布式的分片键写在前面,能“提示”优化器做出正确选择。

尤其在:

  • 多条件 JOIN
  • 多索引可选
  • 成本差距不大的情况下

在 JOIN 的 ON 条件中,MySQL 内部是这样干活的(简化版):

  1. 先挑一个 驱动表
  2. 逐行取驱动表数据
  3. 按 ON 条件顺序,尝试构造索引访问条件
  4. 一旦能用上索引,就“定型”访问方式

— over —

Leave a Comment