✅ 一、什么是驱动表?

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!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐