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分析你的查询,观察驱动表的选择是否符合你的预期。

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

相关推荐
gys9895几秒前
uniapp使用sqlite模块
数据库·sqlite·uni-app
凌冰_37 分钟前
Java Maven+lombok+MySql+HikariCP 操作数据库
java·数据库·maven
武子康39 分钟前
Java-165 Neo4j 图论详解 欧拉路径与欧拉回路 10 分钟跑通:Python NetworkX 判定实战
java·数据库·性能优化·系统架构·nosql·neo4j·图论
岳麓丹枫0011 小时前
pg_stat 视图介绍
数据库·postgresql
弗朗凌戈1 小时前
影院票务管理系统oracle
数据库·oracle·vr
·云扬·1 小时前
MySQL主从数据一致性校验工具:pt-table-checksum 详解
数据库·sql·mysql
wudl55662 小时前
Python 虚拟环境和包管理
数据库·python·sqlite
那我掉的头发算什么2 小时前
【数据库】事务
数据库·sql·mysql·github·数据库开发
全栈工程师修炼指南2 小时前
DBA | Oracle 数据备份迁移之传统 exp/imp 工具实战指南
数据库·oracle·dba
自由日记2 小时前
MySql修炼2(力扣):收了6只妖
数据库·mysql