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(字段)
- 如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加;
- 如果这个“字段”定义允许为 null,那么执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。
查找效率:count(*) ≈ count(1) > count(主键id) >= count(字段)
网友评论