Innodb存储引擎count()计数性能比较

0.前言

MySQL中有九种存储引擎,其中主要的两种引擎分别是:

  • MyISAM
  • InnoDB

MySQL在5.5版本之前的默认引擎为MyISAM,从5.5版本开始默认的存储引擎为InnoDB。

MyISAM不支持事务特性,而InnoDB支持事务。

  • 查看引擎

    mysql>SHOW ENGINES;
    Engine              Support     Transactions
    ------------------  -------     ------------
    FEDERATED           NO          (NULL) 
    MRG_MYISAM          YES         NO
    MyISAM              YES         NO
    BLACKHOLE           YES         NO
    CSV                 YES         NO
    MEMORY              YES         NO
    ARCHIVE             YES         NO
    InnoDB              DEFAULT     YES
    PERFORMANCE_SCHEMA  YES         NO
    
  • 如何查看指定数据库的表结构

    mysql>USE shop;
    mysql>SHOW TABLE STATUS;
    Name          Engine  Version  Row_format    Rows  Avg_row_length
    ------------  ------  -------  ----------  ------  --------------
    shop_order    InnoDB       10  Compact         23             712
    shop_product  InnoDB       10  Compact          4            4096
    shop_user     InnoDB       10  Compact          2            8192
    

在MyISAM存储引擎中,会把一个表的总行数存储在磁盘上。而InnoDB存储引擎由于多版本并发控制(MVCC)机制,对于count(*)应该返回多少行并不确定,只能把数据一行一行从引擎中读出,然后累积计数,无法将总行数存储在磁盘中。

1.InnoDB引擎count(*)计数

1.1存在问题

假设表t中现在有 10000 条记录,我们设计了三个用户并行的会话。

  • 会话 A 先启动事务并查询一次表的总行数;
  • 会话 B 启动事务,插入一行后记录后,查询表的总行数;
  • 会话 C 先启动一个单独的语句,插入一行记录后,查询表的总行数。

我们假设从上到下是按照时间顺序执行的,同一行语句是在同一时刻执行的。

数据库事务会话A、B、C的执行过程

你会看到,在最后一个时刻,三个会话 A、B、C 会同时查询表 t 的总行数,但拿到的结果却不同。

因为InnoDB引擎的默认隔离级别是可重复读,在代码上就是通过多版本并发控制(MVCC)实现的,每一行记录都要判断是否对本会话(Session)是否可见,对于count(*)请求来说,只能把数据一行一行读出依次判断。

1.2数据库保存计数

由于在InnoDB的可重复读隔离级别下,每一个会话创建的视图不一定一致,导致count(*)计数时结果的不一致,这也就是为什么InnoDB引擎不能像MyISAM引擎将表的总行数固定写到磁盘中(MyISAM不支持事务特性)。那么解决这一问题的方式大致可以分为两种:

  • 利用缓存系统保存计数
  • 利用数据库表保存计数

其中利用缓存系统(如Redis)保存计数,由于不能支持事务的强一致性,同样会导致数据不一致的结果,具体例子如下:

缓存系统会话A、B的执行过程

此时,会话B并不能查询到新插入的数据R,但是计数值已经加了1。虽然Redis能正常工作,但是这个计数值在逻辑上是不精确的。

而利用数据库的事务特性就能实现逻辑上的一致性,如下图所示

数据库事务会话A、B的执行过程

由于InnoDB默认隔离级别为可重复读,由于会话B在会话A未提交事务之前开启了事务,所以会话A开启的事务视图与会话B开启的事务视图是一致的,同时会话A的计数值加1操作对于会话B也是不可见的,这就能实现会话B查询最近100条记录在逻辑的一致性。

2.InnoDB引擎下count()性能比较

count():返回对应列值不为null的行数

2.1count(*)

由于聚簇索引下(即主键索引)的索引树每个叶子节点都是整行数据,而非聚簇索引的索引树叶子节点存储的是索引值,所以Server层优化器对于count()进行计数时,会选择非聚簇索引中最小的索引树来遍历。*并不取值进行判断。

2.2count(1)

InnoDB引擎会遍历整张表,但不取值。Server层对于返回的每一行,放入一个数字”1“进去。判断不可能为空,按行累加。

2.3count(主键id)

InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。

count(1) 执行得要比 count(主键 id) 快。因为从引擎返回 id 会涉及到解析数据行,以及拷贝字段值的操作。

2.4count(字段)

  1. 如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加;
  2. 如果这个“字段”定义允许为 null,那么执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。

查找效率:count(*) ≈ count(1) > count(主键id) >= count(字段)

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容