mysql驱动表由优化器基于成本估算自主决定,关键因素是索引可用性、表行数估算和条件过滤强度;可通过explain format=json的"join_order"确认,stright_join可强制指定。

MySQL 为什么有时用 A 表驱动 B,有时反过来?
驱动表的选择不是由 JOIN 顺序决定的,而是优化器基于成本估算(cost-based)自主决策的结果。你写 SELECT * FROM A JOIN B ON A.id = B.a_id,优化器可能实际先扫 B 再匹配 A——只要它算出来这样总代价更低。
关键影响因素有三个:索引可用性、表行数估算、条件过滤强度。其中索引是硬门槛:没合适索引的表几乎不会被选为被驱动表(即“内表”),因为嵌套循环时它要被反复扫描。
- 如果
A.id有主键或唯一索引,B.a_id有普通索引,优化器大概率选 A 当驱动表 - 如果两表都只有全表扫描路径,优化器倾向选小表当驱动表(但“小”指估算行数,不是磁盘大小)
-
WHERE条件落在哪张表上,会显著改变该表的“有效行数”,进而翻转驱动表选择
如何确认当前查询实际用了哪个驱动表?
看 EXPLAIN 输出的 table 列顺序和 type 列组合。真正被驱动的表(即内表)通常表现为:type 是 ref/eq_ref,且 possible_keys 显示用了索引;而驱动表可能是 ALL 或 range,甚至 const。
更直接的方法是加 FORMAT=JSON:执行 EXPLAIN FORMAT=JSON SELECT ...,在输出里找 "join_order" 数组,第一个元素就是驱动表;同时看 "used_columns" 和 "rows_estimated" 验证估算逻辑。
- 别信
EXPLAIN的rows值,它常严重失真,尤其在多条件 or 统计信息过期时 -
SHOW WARNINGS之后能看到优化器重写的 SQL,有时会暴露隐式转换导致索引失效(比如INT列和字符串比较) - 用
optimizer_trace可以看到完整成本计算过程,但开启后有性能开销,仅调试时临时启用
强制指定驱动表的三种现实手段
优化器算错成本很常见,尤其跨版本升级或统计信息陈旧时。人工干预不是“不推荐”,而是得知道怎么干预才靠谱。
- 用
STRAIGHT_JOIN:写在SELECT后,如SELECT STRAIGHT_JOIN * FROM A JOIN B ON ...,它禁用优化器重排,严格按 SQL 中表顺序执行。但风险是:一旦某张表后续数据量突增,性能会断崖下跌 - 给被驱动表加
FORCE INDEX:如FROM A JOIN B FORCE INDEX (idx_a_id) ON ...,这不改变驱动方向,但能确保 B 走索引,间接让优化器更愿意选 A 驱动 - 改写为子查询(慎用):把小结果集先查出来再
JOIN,例如SELECT * FROM (SELECT id FROM A WHERE status=1 LIMIT 1000) t JOIN B ON t.id = B.a_id,本质是把驱动逻辑收归到应用层
注意:USE INDEX 和 IGNORE INDEX 对驱动表选择影响极小,它们只管单表访问路径,不参与 join order 决策。
最容易被忽略的两个成本陷阱
优化器的成本模型里,有些开销它根本没算进去,但线上却真实致命。
- Buffer Pool 污染:驱动表如果是大表全表扫描,会把热数据挤出内存,导致后续查询变慢——优化器只算 I/O 次数,不算缓存置换代价
- 锁等待放大:驱动表扫描过程中,对被驱动表的每次
ref查找都会加锁(即使是READ-COMMITTED)。如果被驱动表索引区分度差(比如大量重复值),锁冲突会指数级上升
所以即使 EXPLAIN 显示“最优”,也得结合 SHOW PROFILE 或 performance_schema 查看实际 Handler_read_next 和 innodb_row_lock_waits 指标。优化器没告诉你这些,但它决定了你的 P99 延迟。











