如何通过小表驱动大表及索引优化MySQL JOIN查询效率?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1110个文字,预计阅读时间需要5分钟。
优化MySQL的JOIN性能,关键不在于让大表变小,而是让优化器选择合适的驱动表,并确保每次内层匹配都走索引。否则,即使只驱动10行,无索引的驱动表也可能扫描10×100万行。
怎么看哪张表实际当了驱动表
别看建表语句或 EXPLAIN 输出里表的顺序,要看 EXPLAIN 中每行的 rows 列:它代表该表经过 WHERE 过滤后预估返回的行数。优化器选的驱动表,就是这个值最小的那张表。
-
LEFT JOIN下左表强制驱动,哪怕它的rows是 50 万,也得硬着上; -
INNER JOIN下优化器会估算所有表过滤后的rows,挑最小的当驱动表; - 如果发现
EXPLAIN显示大表被选为驱动表(比如rows达几十万),说明你的WHERE条件没生效,或者统计信息过期,该跑ANALYZE TABLE了; - 用
STRAIGHT_JOIN强制顺序前,先确认你比优化器更懂数据分布——否则可能把 100 行驱动变成 10 万行驱动。
被驱动表的 ON 字段必须单独建索引
只要某张表在 ON 子句里是“被查”的那一方(即内层循环),它的关联字段就必须有索引。主键、唯一索引、普通索引都行,但不能没有。
- 例如
JOIN orders o ON u.user_id = o.user_id,orders.user_id必须有索引; - 如果是复合条件
ON t1.a = t2.x AND t1.b = t2.y,t2上要建INDEX(x, y),顺序必须和ON中一致; -
INDEX(a, b, c)无法加速ON t2.b = ?,因为b不是最左前缀; -
LEFT JOIN的右表、RIGHT JOIN的左表最容易漏建索引,务必逐个检查ON字段是否已索引。
联合索引字段顺序:JOIN 字段必须放 WHERE 字段前面
MySQL 执行逻辑是:拿驱动表一行 → 用 ON 条件去被驱动表找匹配行 → 再对匹配行做 WHERE 过滤。所以索引必须优先支持 ON 查找,再覆盖 WHERE 过滤。
- 写法
JOIN users u ON o.user_id = u.id WHERE u.status = 'active',索引应建为INDEX(id, status),不是INDEX(status, id); - 即使
status区分度极高,也不能把它放前面——否则ON o.user_id = u.id就没法用索引快速定位; - 如果
WHERE条件区分度远高于JOIN字段(比如id是外键、重复率高,而status是枚举且值极少),可考虑改写 SQL,先子查询过滤再JOIN,而非依赖单索引兼顾两者。
字段类型不一致会让所有索引失效
哪怕只是 INT 和 UNSIGNED INT、VARCHAR(50) 和 VARCHAR(100)、或者一边是字符串一边是数字,MySQL 都可能触发隐式转换,导致被驱动表的索引完全不走。
- 检查
EXPLAIN的type列:如果是ALL或index,而不是ref/eq_ref,大概率是类型不匹配; - 用
SHOW CREATE TABLE对比两张表的关联字段类型,一个字符都不能差; - 避免在
ON里写函数,比如CAST(t1.id AS CHAR) = t2.ref_id或DATE(t1.created_at) = t2.date,这等于主动放弃索引。
最常被忽略的一点:驱动表“小”是动态的,取决于 WHERE 过滤后还剩多少行——不是你印象中“部门表比员工表小”,而是这次查询里,WHERE city = '北京' AND channel = 'APP' 筛完还剩 100 行,它才是真正的驱动表。盯着 EXPLAIN 的 rows 看,比背口诀管用得多。
本文共计1110个文字,预计阅读时间需要5分钟。
优化MySQL的JOIN性能,关键不在于让大表变小,而是让优化器选择合适的驱动表,并确保每次内层匹配都走索引。否则,即使只驱动10行,无索引的驱动表也可能扫描10×100万行。
怎么看哪张表实际当了驱动表
别看建表语句或 EXPLAIN 输出里表的顺序,要看 EXPLAIN 中每行的 rows 列:它代表该表经过 WHERE 过滤后预估返回的行数。优化器选的驱动表,就是这个值最小的那张表。
-
LEFT JOIN下左表强制驱动,哪怕它的rows是 50 万,也得硬着上; -
INNER JOIN下优化器会估算所有表过滤后的rows,挑最小的当驱动表; - 如果发现
EXPLAIN显示大表被选为驱动表(比如rows达几十万),说明你的WHERE条件没生效,或者统计信息过期,该跑ANALYZE TABLE了; - 用
STRAIGHT_JOIN强制顺序前,先确认你比优化器更懂数据分布——否则可能把 100 行驱动变成 10 万行驱动。
被驱动表的 ON 字段必须单独建索引
只要某张表在 ON 子句里是“被查”的那一方(即内层循环),它的关联字段就必须有索引。主键、唯一索引、普通索引都行,但不能没有。
- 例如
JOIN orders o ON u.user_id = o.user_id,orders.user_id必须有索引; - 如果是复合条件
ON t1.a = t2.x AND t1.b = t2.y,t2上要建INDEX(x, y),顺序必须和ON中一致; -
INDEX(a, b, c)无法加速ON t2.b = ?,因为b不是最左前缀; -
LEFT JOIN的右表、RIGHT JOIN的左表最容易漏建索引,务必逐个检查ON字段是否已索引。
联合索引字段顺序:JOIN 字段必须放 WHERE 字段前面
MySQL 执行逻辑是:拿驱动表一行 → 用 ON 条件去被驱动表找匹配行 → 再对匹配行做 WHERE 过滤。所以索引必须优先支持 ON 查找,再覆盖 WHERE 过滤。
- 写法
JOIN users u ON o.user_id = u.id WHERE u.status = 'active',索引应建为INDEX(id, status),不是INDEX(status, id); - 即使
status区分度极高,也不能把它放前面——否则ON o.user_id = u.id就没法用索引快速定位; - 如果
WHERE条件区分度远高于JOIN字段(比如id是外键、重复率高,而status是枚举且值极少),可考虑改写 SQL,先子查询过滤再JOIN,而非依赖单索引兼顾两者。
字段类型不一致会让所有索引失效
哪怕只是 INT 和 UNSIGNED INT、VARCHAR(50) 和 VARCHAR(100)、或者一边是字符串一边是数字,MySQL 都可能触发隐式转换,导致被驱动表的索引完全不走。
- 检查
EXPLAIN的type列:如果是ALL或index,而不是ref/eq_ref,大概率是类型不匹配; - 用
SHOW CREATE TABLE对比两张表的关联字段类型,一个字符都不能差; - 避免在
ON里写函数,比如CAST(t1.id AS CHAR) = t2.ref_id或DATE(t1.created_at) = t2.date,这等于主动放弃索引。
最常被忽略的一点:驱动表“小”是动态的,取决于 WHERE 过滤后还剩多少行——不是你印象中“部门表比员工表小”,而是这次查询里,WHERE city = '北京' AND channel = 'APP' 筛完还剩 100 行,它才是真正的驱动表。盯着 EXPLAIN 的 rows 看,比背口诀管用得多。

