MySQL 小表驱动大表优化原理详解
“小表驱动大表”是 MySQL 中 JOIN 优化时最核心、最常被提及的原则之一,很多面试和实际优化案例都会问到这个点。
下面从原理、执行过程、为什么重要、实际影响、常见误区几个维度给你讲清楚。
1. 核心原理:Nested Loop Join(嵌套循环连接)
MySQL 最常用的 Join 算法是 Nested Loop Join(嵌套循环连接),它的执行方式是:
for each row in 驱动表 (外表)
for each row in 被驱动表 (内表)
if 连接条件成立
输出结果
关键点就在这里:外层循环(驱动表)每执行一次,内层循环(被驱动表)就要完整扫描一次(或者通过索引快速查找)。
所以驱动表执行的次数越少,整个 Join 的总扫描量就越小。
结论:让行数少的表做驱动表(外表),行数多的表做被驱动表(内表),就能显著减少总的扫描行数和 IO 量。
2. 用例子看清楚差距
假设有两张表:
- 小表 user:100 行
- 大表 order:1000 万 行
场景 1:小表驱动大表(推荐)
SELECT *
FROM user u
JOIN order o ON u.id = o.user_id;
执行过程(假设 user_id 有索引):
- 扫描 user 表 100 次(外层循环)
- 每行 user 去 order 表通过索引查找匹配的记录(平均 1 次索引查找)
- 总查找次数 ≈ 100 次
场景 2:大表驱动小表(低效)
SELECT *
FROM order o
JOIN user u ON o.user_id = u.id;
执行过程:
- 扫描 order 表 1000 万次(外层循环)
- 每行 order 去 user 表通过索引查找(平均 1 次)
- 总查找次数 ≈ 1000 万次
差距:100 次 vs 1000 万次,差了 10 万倍!
这就是为什么一定要“小表驱动大表”。
3. MySQL 是怎么决定谁是驱动表、谁是被驱动表的?
MySQL 优化器主要看以下因素(优先级从高到低):
- 显式写法(FROM 后面的表顺序)
早期版本会倾向于把 FROM 第一个表作为驱动表(但现在优化器更智能) - 表行数(统计信息)
优化器通过information_schema.tables或 innodb_table_stats 中的 rows 字段估算表的大小
行数少的表更大概率被选为驱动表 - 索引情况
被驱动表上连接字段是否有高效索引(ref、eq_ref、range)
有索引的表更容易被选为被驱动表 - 过滤条件后的预计行数(最重要)
优化器会根据 WHERE 条件、JOIN 条件估算每个表过滤后的行数
过滤后行数少的表,更容易成为驱动表
EXPLAIN 看 driving table:
explain select ... from small_table s join big_table b on ...
看 Extra 列:
Using join buffer (Block Nested Loop)→ 块嵌套循环(大表驱动小表常见)- 没有 join buffer → 通常是小表驱动大表
4. 什么时候“小表驱动大表”会被打破?
- STRAIGHT_JOIN 强制指定驱动表顺序
SELECT * FROM big_table STRAIGHT_JOIN small_table ON ...
- 被驱动表连接字段没有索引 → 可能退化为全表扫描,代价极大
- 子查询转 JOIN 时,子查询结果集很大
- 优化器统计信息不准(最常见坑)
5. 实际优化建议(生产环境最常用)
- 写 SQL 时尽量把小表写在前面(养成习惯,但不要完全依赖)
- 在连接字段上建索引(被驱动表必须有)
- 定期执行 ANALYZE TABLE 更新统计信息
- 大表 JOIN 前加过滤条件,尽量把数据量打小
SELECT *
FROM user u
JOIN order o ON u.id = o.user_id
WHERE u.status = 'active' AND o.create_time > '2025-01-01';
- 如果表大小差距极大且无法优化,考虑:
- 把小表查出来后在代码里循环查大表(分批)
- 用临时表或物化视图
- 业务上避免这种 JOIN
6. 总结一句话口诀
“小表驱动大表,减少嵌套循环的扫描次数;被驱动表要有索引,连接字段要高效。”
面试回答模板(简洁版):
“因为 MySQL 默认使用 Nested Loop Join,驱动表每行都要去被驱动表匹配一次,所以让行数少的表做驱动表,可以大幅减少总的扫描和匹配次数。优化器会根据表行数、索引情况、过滤后行数来选择驱动表,所以我们写 SQL 时尽量把小表放前面,并确保被驱动表的连接字段有索引。”
你最近遇到过 JOIN 性能问题吗?是哪种场景?可以贴执行计划我帮你看下。