Mysql小表驱动大表优化原理

它触及了MySQL查询性能优化的一个核心原则。简单来说,"小表驱动大表" 的核心目的是为了减少磁盘I/O操作和比较次数,从而提升查询效率。

下面我们通过几个层面来详细解释为什么。

1. 从嵌套循环的概念理解

想象一下MySQL如何执行一个关联查询(比如使用JOININ/EXISTS子查询)。在大多数情况下,尤其是在没有高效索引时,它的底层操作类似于一个"嵌套循环"。

  • 驱动表(外层循环): 首先被访问的表,遍历它的每一行。
  • 被驱动表(内层循环): 针对驱动表的每一行,去这个表里查找匹配的数据。

场景对比:

假设我们有两张表:

  • 小表: 有100条记录。
  • 大表: 有10万条记录。

情况一:大表驱动小表(错误的做法)

sql 复制代码
-- 假设MySQL选择大表作为驱动表(通常不会,这里是为了对比)
for (每条记录 in 大表) { // 循环10万次
    for (每条记录 in 小表) { // 每次循环100次
        if (条件匹配) {
            输出结果;
        }
    }
}

总比较次数: 10万 * 100 = 1000万次。

情况二:小表驱动大表(正确的做法)

sql 复制代码
for (每条记录 in 小表) { // 循环100次
    for (每条记录 in 大表) { // 每次循环10万次
        if (条件匹配) {
            输出结果;
        }
    }
}

总比较次数: 100 * 10万 = 1000万次。

咦? 从上面的简单计算来看,总比较次数是一样的啊?为什么说小表驱动大表更快?

2. 关键因素:磁盘I/O和索引

上面的例子假设的是全表扫描,并且没有考虑最重要的因素:磁盘I/O。数据库的数据是存储在磁盘上的,而操作是在内存中进行的。将数据从磁盘读入内存是数据库操作中最耗时的部分。

现在,我们引入索引,特别是被驱动表上的索引。

情况二(优化版):小表驱动大表,且大表(被驱动表)的关联字段有索引

sql 复制代码
for (每条记录 in 小表) { // 循环100次,将小表数据读入内存
    // 根据小表当前记录的关联字段值,去大表的索引(B+Tree)中进行查找
    // 索引查找非常快,近似于O(log n)的复杂度,假设3次磁盘I/O就能找到
    if (在大表中通过索引找到匹配记录) { // 每次循环约3次I/O
        输出结果;
    }
}

总磁盘I/O次数估算: 100(次小表循环) * 3(次索引查找I/O) ≈ 300次磁盘I/O。

情况一(优化版):大表驱动小表,且小表(被驱动表)的关联字段有索引

sql 复制代码
for (每条记录 in 大表) { // 循环10万次,需要分批将大表数据读入内存,I/O量巨大
    // 根据大表当前记录的关联字段值,去小表的索引中查找
    if (在小表中通过索引找到匹配记录) { // 每次循环约3次I/O
        输出结果;
    }
}

总磁盘I/O次数估算: 10万(次大表循环) * 3(次索引查找I/O) ≈ 30万次磁盘I/O。

结论对比:

驱动表 被驱动表索引 总比较次数(理论) 总磁盘I/O次数(核心影响)
大表 1000万 极高(全表扫描10万*100次)
小表 1000万 极高(全表扫描100*10万次)
小表 100 * log(10万) 极低(约300次)
大表 10万 * log(100) 较高(约30万次)

可以看到,当被驱动表的关联字段上有索引时,"小表驱动大表"的策略能将内层循环的全表扫描转换为高效的索引查找,从而极大地减少了磁盘I/O次数。 即使被驱动表没有索引,用小表驱动也能减少外层循环的次数,虽然内层循环仍然是全表扫描,但总体开销通常也更小。

3. 这个原则在SQL中的体现

  1. IN vs EXISTS

    • IN 适合子查询(内表)是小表的情况。

      sql 复制代码
      SELECT * FROM A WHERE id IN (SELECT id FROM B)

      IN会先执行(SELECT id FROM B),得到一个小结果集(内表),然后用A表的id去遍历这个小结果集。这里B表是小表,作为驱动表是高效的。

    • EXISTS 适合主查询(外表)是小表的情况。

      sql 复制代码
      SELECT * FROM A WHERE EXISTS (SELECT 1 FROM B WHERE B.id = A.id)

      EXISTS会先遍历A表(外层循环),对于A表的每一行,去执行子查询判断是否存在。如果A表是小表,B表是大表且B.id有索引,这种写法就非常高效。

  2. JOIN MySQL的优化器会尝试自动选择最好的驱动表。你可以通过EXPLAIN命令查看执行计划。在EXPLAIN的输出中,排在第一行的表就是驱动表。

    • 优化器会根据表的大小 (行数)、索引过滤条件等因素来选择驱动表。
    • 你通常不需要手动指定,但要理解优化器为什么会这么选。例如,如果你给被驱动表的关联字段加上了索引,就相当于为优化器选择"小表驱动大表"的策略铺平了道路。

总结

为什么是小表驱动大表?

  1. 核心目标:减少磁盘I/O,这是数据库性能的瓶颈。
  2. 实现方式: 通过减少外层循环的次数,并将内层循环的全表扫描转换为高效的索引查找
  3. 实践指导:
    • 确保你的JOIN查询或子查询中,被驱动表的关联字段上建立了索引。这是让"小表驱动大表"原则生效的前提。
    • 在编写INEXISTS子查询时,有意识地思考哪个表更小,从而选择更合适的写法。
    • 多使用EXPLAIN分析你的查询,观察驱动表的选择是否符合你的预期。

记住这个口诀:小表驱动大表,索引建在被驱动表上。

相关推荐
海上彼尚19 小时前
Go之路 - 7.go的结构体
开发语言·后端·golang
w_t_y_y1 天前
Nginx Plus
运维·数据库·nginx
源代码•宸1 天前
分布式缓存-GO(分布式算法之一致性哈希、缓存对外服务化)
开发语言·经验分享·分布式·后端·算法·缓存·golang
川贝枇杷膏cbppg1 天前
dm_unknown_202512.log:达梦数据库 “未分类日志“
数据库·oracle
It's now1 天前
Spring AI 基础开发流程
java·人工智能·后端·spring
计算机毕设VX:Fegn08951 天前
计算机毕业设计|基于springboot + vue图书商城系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
求学中--1 天前
MySQL 数据库完整操作命令与使用指南
数据库·sql·mysql·oracle
夕颜1111 天前
BeeAI 框架学习记录
后端
极市平台1 天前
骁龙大赛-技术分享第5期(上)
人工智能·经验分享·笔记·后端·个人开发
DKunYu1 天前
误删数据库表导致出现1146报错
数据库