小表驱动大表的本质是嵌套循环连接中以过滤后结果集小的表为驱动表;优化器依据预估扫描行数而非物理行数选驱动表,统计信息过期或被驱动表缺失索引将导致性能劣化。

小表驱动大表不是玄学,而是嵌套循环连接(Nested Loop Join)在有索引前提下的必然最优路径;但直接写 JOIN 语句时,MySQL 优化器未必真按你想象的“小表”来驱动——它看的是过滤后的结果集大小,不是物理表行数。
为什么 EXPLAIN 显示小表没被选为驱动表?
优化器决定驱动表的依据是「预估扫描行数」,而非建表时的行数。比如:
-
users表有 200 万行,但加了WHERE city = '北京' AND registration_channel = 'APP'后只剩 80 万行 → 它就不再是“小表” -
orders表虽有 200 万行,但WHERE order_status IN (1,2)只筛出 50 万行 → 这个子查询结果集才可能是真正的驱动源 - 如果统计信息过期(
ANALYZE TABLE没跑),优化器会误判行数,导致选错驱动表
如何强制让小结果集驱动大表?
别依赖优化器自动识别,尤其在复杂 WHERE 或多表 JOIN 场景下。实操建议:
- 用子查询封装小结果集:
SELECT * FROM (SELECT user_id FROM orders WHERE order_status IN (1,2)) t JOIN users u ON t.user_id = u.user_id—— 让优化器一眼看清驱动侧只有几十万行 - 对
LEFT JOIN,显式用STRAIGHT_JOIN:如SELECT STRAIGHT_JOIN ... FROM small_table s LEFT JOIN large_table l ON s.id = l.id,跳过成本估算,直选顺序 - 避免在被驱动表的
ON字段上做函数操作,例如ON DATE(l.create_time) = '2026-04-23'会让索引失效,等于放弃 Nested Loop 的加速前提
被驱动表没索引时,“小表驱动”毫无意义
Nested Loop 的性能优势完全建立在「被驱动表能快速定位匹配行」的基础上。如果 large_table 的 JOIN 字段没索引:
- 每次外层循环都要全表扫描大表 → 总扫描量 = 小表行数 × 大表行数
- 哪怕小表只有 100 行,大表 1000 万行,也要扫 10 亿行
- 此时加索引比调换
JOIN顺序重要 10 倍;优先执行:ALTER TABLE large_table ADD INDEX idx_join_col (join_col) - 注意字段类型必须严格一致(如都是
BIGINT),否则隐式转换会让索引失效
真正卡住性能的,往往不是“该不该小表驱动”,而是“你以为的小表,经过过滤后还是小表吗?”以及“被驱动表那列,到底有没有走索引?”——这两个问题不确认,所有 STRAIGHT_JOIN 和子查询改写都只是隔靴搔痒。










