《MySQL45讲》读书笔记(十):count

此文为极客时间:MySQL实战45讲的14节count相关内容的笔记

一、count的查询过程

count() 函数用于查询数据的数量,不同的引擎有不同的实现。

在 MySAM 引擎会专门记录表的总数;而在 innodb 中,由于 MVCC 的原因,在统计数量的时候需要一行一行的判断当前数据是否对查询的事务的可见的。此外,show table status的 Rows 字段也能获取表的总条数,但是这只是预估值,实际上并不准确。

因此,表的遍历是无法避免的,但是 innodb 也有针对此做出优化:

主键索引树的叶子节点是数据行,而普通索引树的叶子节点是主键,针对 count 操作遍历那颗树都是一样的,因此查询的时候回找到最小的那颗索引树进行遍历。

二、计数操作的实现

计数这个功能通过 redis 也可以实现,即在 redis 中设定一个值用于计数,但是在遇到异常停止服务后不免会遇到数据丢失的问题。

如果结合 mysql 来做,即 redis 更新数据后再变更数据库里的数据,读取的时候直接从 redis 读,这样虽然一定程度避免了数据丢失的问题,但是却有可能导致数据不一致。举个例子:

现在计数是5,我们要为计数+1

  • 先更新 redis 再更新 mysql:如果中间其他线程进行了一次查询,此时数据库是5,但是却查到了6
  • 先更新 mysql 在更新 redis:如果中间其他线程进行了一次查询,此时数据库是6,但是却查到了5

而如果使用 mysql,借助 innodb 可重复读下事务隔离的特性,我们就可以比较好的避免上述问题。

另外,值得一提的是,由于 innodb 的两阶段提交机制,更新获取的行锁会等到事务提交才释放,所以最好先插入再更新

三、不同 count 的区别

我们常见的 count 的用法一般有这三种:count(*),count(1),count(id)。下面就来描述一下这三种的区别。

count() 是一个聚合函数,他会判断括号中的条件是否为 null,不是就累加1。因此,对于 count(id) 这样涉及字段的操作,一般流程是这样的:

  • 遍历数据行
  • 取出 id 字段的值并返回
  • 判断 id 不为 null
  • 计数+1

而 count(1) 则是这样:

  • 遍历数据行
  • 直接返回 1
  • 判断 1 不为 null
  • 计数+1

count(*) 专门做了优化,流程与 count(1) 差不多。

因此,不难理解,由于需要去解析数据并且取出字段值,相对于不需要这些操作的 count(1) 和 count(*) 来说,速度是要慢一些的。而 count(字段) 相比起作为主键索引树上的 id,如果没有专门建索引,速度只会比 count(id) 更慢。

也就是说,他们速度是这样的:**count(字段) < count(id) <count(1) ≈ count(*)**

四、总结

MySAM 会专门记录条数,查询的时候直接返回。innodb 由于 MVCC 机制, count 查询需要遍历数据以判断事务可见性。

redis 计数不能保证数据不丢失,redis + mysql 缓存和数据库数据可能会不一致,借助 innodb 的事务可以避免以上问题。

因为 innodb 的两阶段提交机制,最好先插入再更新。

查询速度:count(字段) < count(id) <count(1) ≈ count(*)

0%