没有特殊说明,以下均是针对哦InnoDB引擎的说明

实现方式

不同的MySQL引擎的实现方式不同

  • MyISAM引擎把一个表的总行数存在了磁盘上,因此执行无条件count(*)的时候会直接返回这个数,效率很高
  • InnoDB引擎由于有MVCC机制,针对不同的视图,放回的条数不同,需要根据扫描到行数进行计数,这就导致InnoDB引擎的count()操作当数据量增大的时候效率会变慢

    单独对总数进行缓存

    对于需要查询总数的业务场景,可以将总数计数放到缓存中单独存储,这样逻辑上就应该有两步操作:

  • 插入记录

  • 计数+1

    数据一致性问题

    假设将计数放到Redis中

  • 可能会有缓存计数丢失问题,假设Redis的缓存还没有持久化就异常重启了,Redis重启后就丢失了一次+1操作

    • 这个问题可以通过每次Redis重启的时候从数据库中取一次真实值,因为Redis也不会经常重启,偶尔一次查询慢也没问题
  • 假设在执行insert之后,还没有更新Redis计数,查询时查询到了insert的记录,但是总数还是insert之前的,反过来也一样

    • 有人可能会提出将insert和更新Redis计数放到一个事务中,事务提交之前,insert的记录是其他事务不可见的,其实还会有问题,假如Redis计数更新后,事务没有提交,那是不是会出现insert之后的总数被查到了,而insert的记录查不到

      解决数据一致性问题

      通过数据库事务来解决,将计数放到MySQL中,通过事务的隔离性来解决这个问题,同时也MySQL InnoDB引擎有崩溃恢复机制,还能避免Redis持久化前异常重启丢失计数问题。

      不同的count的差别

      count()是一个聚合函数,对于返回的结果集,一行一行地判断,如果count函数不是NULL,计数就加1.最后返回计数。
  • count(主键):InnoDB引擎会遍历整张表,把每一行的主键值都取出来,返回给server层,server层拿到id后,判断是不可能为空的,计数加一

  • count(字段):
    • 如果该字段定义为not null,判断到不能为null,就一行行的读出这个字段来按行累加计数
    • 如果该字段定义为null,判断到可以为null,读出来字段后,还要把取出来的值再判断一下(猜测这一步是在server层完成的,InnoDB只负责读数据),不是null才累加
  • count(1):InnoDB变量整张表,但不取值,server层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加
  • count():并不会把全部字段取出来,而是专门做了优化,不取值,count()肯定不是null,按行累加

count(1)执行效率要比count(主键)快,因为count(主键)涉及数据的读取解析,而count(字段)比count(主键)效率要低。