为什么 MySQL 建议用小表作为驱动表
文章摘要: 在SQL JOIN查询中,驱动表是先被扫描的表,其每一行都会在被驱动表中查找匹配数据。建议小表作为驱动表,可显著减少I/O次数和查询成本。例如,100行小表驱动100,000行大表(带索引)仅需100次查找,反之则需100,000次。核心原理是嵌套循环算法(NLJ),外层表越小循环次数越少。若被驱动表无索引会导致性能灾难(如100×100,000次扫描)。MySQL优化器通常会自动选择
·
✅ 一、什么是驱动表?
在 JOIN
查询中:
- 驱动表(Driving Table):先被读取的表,它的每一行都会去另一张表(被驱动表)中查找匹配数据。
- 类似于嵌套循环:
for row in 驱动表:
lookup matching rows in 被驱动表
例如:
SELECT * FROM A JOIN B ON A.id = B.a_id;
- 如果 A 是驱动表,则:逐行扫描 A,对每行去 B 中找匹配项。
✅ 二、为什么建议“小表做驱动表”?
🎯 核心原因:减少 I/O 次数和整体查询成本
我们通过一个例子来说明:
示例场景:
- 表 A(小表):100 条记录
- 表 B(大表):100,000 条记录
B.a_id
上有索引
情况1:小表 A 为驱动表
SELECT * FROM A JOIN B ON A.id = B.a_id;
执行过程:
for each of 100 rows in A:
使用索引在 B 中查找匹配的 a_id → O(log n) 查找
总查找次数:100 次索引查找
✅ 效率高!
情况2:大表 B 为驱动表(反例)
SELECT * FROM B JOIN A ON B.a_id = A.id;
执行过程:
for each of 100,000 rows in B:
使用索引在 A 中查找匹配的 id
总查找次数:100,000 次索引查找
❌ 效率低!即使每次查找很快,总量也大。
🔍 对比总结:
方案 | 驱动表大小 | 查找次数 | 性能 |
---|---|---|---|
小表驱动 | 100 | 100 次 | ✅ 快 |
大表驱动 | 100,000 | 100,000 次 | ❌ 慢 |
即使每次查找只要 0.1ms,100,000 次就是 10 秒!
✅ 三、背后的算法:Nested Loop Join(嵌套循环)
MySQL 的 JOIN
多数情况下使用 NLJ(Nested Loop Join) 算法:
for row_a in table_a: # 外层循环(驱动表)
for row_b in table_b: # 内层循环(被驱动表)
if match(row_a, row_b):
output(row_a, row_b)
- 外层表越小,外循环次数越少 → 总体时间越短
✅ 四、如果被驱动表没有索引?更可怕!
继续上面的例子,如果 B.a_id
没有索引:
- 每次查找都要全表扫描 B(100,000 行)
- 总扫描量:100 × 100,000 = 10,000,000 行
这就是所谓的 “笛卡尔积爆炸”,性能极差。
⚠️ 所以:被驱动表的关联字段一定要有索引!
✅ 五、MySQL 会自动选择驱动表吗?
是的!现代版本的 MySQL 优化器会基于统计信息(如表行数、索引情况)自动选择更优的驱动表。
你可以通过 EXPLAIN
查看哪个是驱动表:
EXPLAIN SELECT * FROM A JOIN B ON A.id = B.a_id;
id
相同时,排在前面的是驱动表- 或看
rows
列,较小的可能是驱动表
但:
- 统计信息不准时,可能选错
- 复杂查询中优化器未必最优
👉 所以:理解原理,必要时可通过 STRAIGHT_JOIN
强制指定驱动表
✅ 六、例外情况:Batched Key Access (BKA) / Hash Join
- BKA:MySQL 5.6+ 支持,可以批量处理驱动表的行,提升效率。
- Hash Join:MySQL 8.0+ 在某些场景下支持,性能更好,对驱动表要求降低。
但 NLJ 仍是主流,小表驱动原则依然适用。
✅ 总结
项目 | 说明 |
---|---|
驱动表 | 先扫描的表,控制外层循环次数 |
为什么小表驱动 | 减少内层查找/扫描次数,降低总成本 |
核心算法 | Nested Loop Join(嵌套循环) |
最佳实践 | 小表驱动 + 被驱动表有索引 |
工具验证 | 用 EXPLAIN 查看执行计划 |
🎯 一句话记住:
“小表驱动,就像用小钥匙串去开大门,而不是拿大门去试每把小锁。”
掌握这个原则,你就能写出高效的 JOIN
查询,避免慢 SQL!
更多推荐
所有评论(0)