驱动表由EXPLAIN中id最小的表决定,而非rows最小的表;INNER JOIN可重排顺序,LEFT JOIN因语义约束基本不可重排;统计信息不准会导致优化器误判,需定期ANALYZE TABLE;LEFT JOIN中应将右表过滤条件写入ON而非WHERE以实现下推。

EXPLAIN里rows最小的表不一定是驱动表
很多人误以为EXPLAIN输出中rows值最小的表就是驱动表,其实不然。MySQL优化器看的是「过滤后参与JOIN的行数」,也就是rows × filtered的乘积。比如一张表rows = 10000但filtered = 1%,实际只有100行进入JOIN循环;另一张表rows = 500但filtered = 90%,反而有450行要被驱动——前者更可能被选为驱动表。
真正决定驱动关系的是执行计划中id列:id值越小,越早被访问,对应的就是驱动表。别只盯rows,要看id和type组合——如果第一个id的type是ALL或index,说明它在全表/全索引扫描,大概率就是驱动表,且很可能选错了。
INNER JOIN能重排,LEFT JOIN基本不能动
优化器对INNER JOIN有充分自由度:它会穷举所有left-deep连接顺序,基于成本估算挑最优解。但LEFT JOIN语义上锁死了左表必须全量保留,这极大限制了重排空间。即使右表数据少、索引好,优化器也不敢轻易把它提前,否则无法保证左表所有行都出现在结果里。
- 如果你写的
A LEFT JOIN B很慢,不要尝试改成B LEFT JOIN A——结果语义完全不同 - 如果业务上其实不需要保留A中无匹配的NULL行,就该直接改成
A INNER JOIN B,把选择权交还给优化器 -
STRAIGHT_JOIN在LEFT JOIN场景下基本无效,因为语义约束比成本估算更强
统计信息不准时,优化器会“信誓旦旦地选错”
MySQL优化器不读真实数据,只依赖采样估算。当出现以下情况时,它给出的rows和filtered可能严重失真:
- 批量导入后没跑
ANALYZE TABLE,索引基数还是旧的 - 某字段存在严重数据倾斜(比如99%的
status是'pending',但优化器按均匀分布估算) - InnoDB采样页数不足,默认只看8个叶子页,大表容易误判
这时你看到EXPLAIN里显示“驱动表只有100行”,实际执行扫了百万行——不是SQL写得不对,是优化器被统计信息骗了。临时解法是STRAIGHT_JOIN,长期解法是定期ANALYZE TABLE,必要时用FORCE INDEX配合调整。
MySQL 数据库管理技能,能够通过自然语言查询和管理数据库,支持 SELECT/INSERT/UPDATE/DELETE、表管理、备份恢复等操作。当用户提及 MySQL、数据库查询、建表或数据备份时使用此技能。
ON里混WHERE条件,等于主动堵死优化器的路
把本该写在ON子句里的右表过滤条件挪到WHERE,表面看只是位置不同,实则彻底改变优化器的推理路径。例如:
SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'shipped';
这条语句会让优化器认为:必须先拿到所有users,再为每行去orders查匹配,最后才筛status。哪怕orders.status有索引,也救不了JOIN阶段的全量探测。
正确做法是把过滤下推到ON里:
SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'shipped';
这样优化器就知道:右表只需加载status = 'shipped'的那部分数据,大幅缩小被驱动表的扫描范围。注意:这种改写只适用于LEFT JOIN,INNER JOIN下WHERE和ON效果等价,但习惯上仍建议把关联条件放ON、过滤条件放WHERE。


















