小表驱动大表的本质是执行计划中驱动表先被扫描且触发对被驱动表的多次查找,而非SQL书写顺序;真正的小表指WHERE过滤后的结果集大小,需通过EXPLAIN观察type和rows确认,被驱动表必须在JOIN字段上有有效索引。

直接结论:小表驱动大表不是写法顺序,而是执行计划里谁先被扫描、谁触发对另一张表的多次查找。盲目调换FROM/JOIN顺序没用,得看EXPLAIN里type和rows的实际表现。
怎么看哪张表真是“小表”?别信物理行数
所谓“小表”,指的是JOIN前经过WHERE过滤后的真实结果集大小,不是SELECT COUNT(*) FROM table出来的原始行数。
- 比如
orders有200万行,但加了WHERE status IN (1,2) AND create_time > '2026-04-01'后只剩5万行——它就是实际的“小表” - 而
users虽只有100万行,若没加任何过滤条件,就仍是100万行驱动量 - MySQL的
EXPLAIN中,驱动表的type通常是ALL或index,且rows列数值要明显更小;被驱动表的type应为ref或eq_ref,且Extra含Using index
LEFT JOIN为什么不能靠重排来优化?
LEFT JOIN语法强制左表为驱动表,优化器无法交换顺序——哪怕右表更小、索引更优,也无济于事。
- 现象:
EXPLAIN显示左表type=ALL、rows=1.2e6,右表type=ref但响应仍慢 - 原因:
ON条件不参与驱动表过滤,只有WHERE才真正缩小驱动集 - 解法:如果业务允许,改写为
INNER JOIN;否则必须在LEFT JOIN的左表上加WHERE条件(如WHERE u.city = '北京' AND u.status = 'active'),而不是依赖ON里的关联字段
多表JOIN时,优化器容易误判,怎么办?
当JOIN表超过4个,尤其混有多个百行维表和一个千万级事实表时,MySQL或PostgreSQL的优化器可能因成本估算失真,选错驱动顺序。
- MySQL 8.0.18之前默认用Nested Loop,小表放左端是安全策略;8.0.19+启用
hash_join=on后行为更复杂,但别依赖——默认仍按传统规则设计SQL - PostgreSQL会枚举JOIN顺序,但严重依赖
ANALYZE统计信息;若n_distinct不准(比如函数索引未被识别),它可能把热点值当均匀分布,导致小表反复扫描 - 稳做法:用子查询或CTE提前聚合最小的结果集,例如
(SELECT user_id FROM orders WHERE status IN (1,2) GROUP BY user_id),再拿这个临时结果去JOIN其他表
被驱动表没索引,小表驱动大表就白搭
驱动表再小,如果被驱动表的JOIN字段没索引,每次匹配都变成全表扫描,性能直接崩盘。
- 确认方式:
EXPLAIN中被驱动表的type是ALL,key列为NULL - 注意点:复合索引必须满足最左前缀,且
JOIN ON a.x = b.y中,索引必须建在b.y上,而非a.x - 特殊情况:大表JOIN字段存在大量NULL或重复值(如
tenant_id IS NULL占30%),会导致索引选择性差,此时需配合WHERE tenant_id IS NOT NULL显式排除,或对NULL打散处理
真正卡住人的从来不是“要不要小表驱动大表”,而是搞不清WHERE过滤后谁才是那个“小”结果集,以及被驱动表的索引到底有没有被用上——这两点一错,所有顺序调整都是空转。


















