博客
关于我
SELECT COUNT(*) 底层究竟干了啥么?
阅读量:796 次
发布时间:2023-03-22

本文共 2316 字,大约阅读时间需要 7 分钟。

InnoDB vs MyISAM: MySQL COUNT(*)性能对比

在数据库应用中,COUNT(*) 是一个极为常见的 SQL 操作。作为 MySQL 的默认事务引擎,InnoDB 在大多数业务场景下被广泛使用。然而,关于 COUNT(*) 的执行机制和性能表现,许多开发者对其内部实现细节还存在诸多疑惑。本文将深入剖析 InnoDB 和 MyISAM 存储引擎中 COUNT(*) 操作的实现方式及其性能差异。


一、InnoDB 全表 COUNT(*) 的执行过程

1. 执行框架

InnoDB 的 COUNT(*) 操作采用全表扫描的方式,每个操作都需要执行一个循环来逐行计数。具体流程如下:

  • 循环结构:从存储引擎层面看,这是一次典型的表扫描操作。循环体内,会依次读取每一行数据,并根据需要对 COUNT 值进行更新。

  • 读取与计数:每次循环迭代的核心工作包括两部分:

    • 读取一行:通过 row_search_mvcc 函数从 B+ 树索引中读取所需的行数据。这一过程需要处理行锁、MVCC (多版本并发控制) 及行可见性等问题。
    • 计数逻辑:调用 evaluate_join_record 函数,评估当前行的数据是否需要计入 COUNT(*)。具体规则如下:
      • 如果是 COUNT(*),则判断整行数据是否为 NULL。若不是 NULL,则增加计数器。
      • 如果是 COUNT(col_name),则检查 col_name 是否为 NULL。若 col_nameNOT NULL 且值不为 NULL,则计入计数器。
  • 2. 可见性与行可见性

    InnoDB 的行可见性在 COUNT(*) 操作中起着关键作用。以下是相关问题的解答:

    • 问题SELECT COUNT(*) FROM tSELECT MIN(id) FROM t 是否会优先读取表 t 中主键列的最小记录?

    • 回答:不一定。由于行可见性机制的存在,事务读取到的数据视图可能与物理存储中的数据不一致。具体行为由 join_read_firstjoin_read_next 函数决定,这些函数最终都会调用 row_search_mvcc 来获取数据。

    • 问题:在 RU (读未提交) 隔离级别下,如果在 COUNT(*) 执行期间有 DML 语句修改数据,修改操作是否会影响 COUNT(*) 的结果?

    • 回答:不会。MySQL 采用“读到什么就是什么”的原则,即使后续有数据修改操作,已经读取到的数据视图不会受到影响。


    二、数据结构与 COUNT 值存储

    1. COUNT 值的存储

    在 SQL 解析阶段,COUNT(*) 会被转化为一个结果列。具体来说:

    • 结果列的类型为 Item_sum_count
    • COUNT 值存储在该结果列的成员变量 count 中。

    以下是代码示例:

    SELECT COUNT(*) FROM t;

    在执行阶段,会创建一个 Item_sum_count 对象,其中 count 成员变量记录当前的计数值。


    三、MyISAM 全表 COUNT(*) 的实现

    虽然 MyISAM 在大多数生产环境中不再被广泛使用,但其 COUNT(*) 的实现仍值得关注:

  • 执行效率:MyISAM 的 COUNT(*) 操作的时间复杂度为 O(1),因为其直接从表的元数据中获取行数。

  • 行数存储

    • MyISAM 表中存储了两个 row_count 变量:
      • 内存中的 row_count 用于快速获取当前可见的行数。
      • 文件中的 row_count 用于初始时对内存值进行校准。
  • 查询执行SELECT COUNT(*) FROM t 会直接读取内存中的 row_count 值,而无需进行表扫描操作。

  • 优化机制:在 SQL 解析阶段,MyISAM 存储引擎会向优化器提供一个 hint,表明该表的行数可以直接获取,无需进入执行器阶段。


  • 四、常见问题与解答

    1. MyISAM 与 InnoDB 在 COUNT(*) 操作中的主要区别

    • 共性

      • SQL 解析阶段的数据结构一致,COUNT(*) 的结果列类型为 Item_sum_count
      • 返回结果的方式也一致,COUNT 值通过通信协议传递给客户端。
    • 区别

      • MyISAM 表本身维护了一个精确的 row_count,在 SQL 优化阶段通过 hint 提供提示。
      • InnoDB 由于 MVCC 和行可见性机制,无法提供一个统一的行数。

    2. 为什么 InnoDB 无法维护 row_count 变量?

    • 原因:MVCC 机制导致每个事务读到的数据视图可能与实际存储数据不一致。同一表在不同事务中的 row_count 可能会有差异。

    • 影响:MySQL 服务器无法为所有用户线程提供一个统一的 COUNT(*) 结果。

    3. InnoDB COUNT(*) 是否会读取溢出页?

    • 回答:不会。由于 COUNT(*) 只读主键索引页内的数据,且每行的主键值不为 NULL,因此无需读取溢出页。

    五、总结

    COUNT(*) 是一个常见的 SQL 操作,对理解数据库引擎的内部机制至关重要。通过本文的分析可以看出:

    • InnoDB 的 COUNT(*) 采用全表扫描方式,时间复杂度为 O(N),且在读取数据时需要处理行可见性问题。
    • MyISAM 的 COUNT(*) 由于其元数据存储机制,能够以 O(1) 时间复杂度快速获取行数。

    对于开发者来说,选择合适的存储引擎不仅取决于 COUNT(*) 的性能,还需要综合考虑事务隔离级别、数据可见性以及具体业务需求。

    欢迎加入我的技术交流群,讨论更多数据库优化技巧!

    转载地址:http://fsqfk.baihongyu.com/

    你可能感兴趣的文章
    Objective-C实现特征脸算法(附完整源码)
    查看>>
    Objective-C实现状态模式(附完整源码)
    查看>>
    Objective-C实现狄克斯特拉算法(附完整源码)
    查看>>
    Objective-C实现狄克斯特拉算法(附完整源码)
    查看>>
    Objective-C实现猜数字游戏(附完整源码)
    查看>>
    Objective-C实现猜数字算法(附完整源码)
    查看>>
    Objective-C实现猴子爬山算法(附完整源码)
    查看>>
    Objective-C实现环形缓冲区(附完整源码)
    查看>>
    Objective-C实现生产者和消费者问题(附完整源码)
    查看>>
    Objective-C实现生产者消费者问题(附完整源码)
    查看>>
    Objective-C实现生成 Mandelbrot 曼德勃罗集图像算法 (附完整源码)
    查看>>
    Objective-C实现生成崩溃dump文件 (附完整源码)
    查看>>
    Objective-C实现生成数组的所有不同排列算法(附完整源码)
    查看>>
    Objective-C实现生成正态分布数据(附完整源码)
    查看>>
    Objective-C实现生成随机高斯分布(附完整源码)
    查看>>
    Objective-C实现用 PIL 改变对比度算法(附完整源码)
    查看>>
    Objective-C实现用二维数组实现矩阵的转置(附完整源码)
    查看>>
    Objective-C实现用半正弦公式计算两个坐标之间的距离算法 (附完整源码)
    查看>>
    Objective-C实现用卡方解密凯撒算法(附完整源码)
    查看>>
    Objective-C实现用蒙特卡洛方法计算圆周率PI算法(附完整源码)
    查看>>