count(1) 比 count(*) 效率高么

有 Where 条件的 count,会根据扫码结果count 一下所有的行数,其性能更依赖于你的 Where 条件

MyISAM 引擎会把一个表的总行数记录了下来,所以在执行 count(*) 的时候会直接返回数量,执行效率很高。

在 MySQL 5.5 以后默认引擎切换为 InnoDB,InnoDB 因为增加了版本控制(MVCC)的原因,同时有多个事务访问数据并且有更新操作的时候,每个事务需要维护自己的可见性,那么每个事务查询到的行数也是不同的,所以不能缓存具体的行数,他每次都需要 count 一下所有的行数。

官方解释

InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.

InnoDB 处理 SELECT COUNT(*) 和 SELECT COUNT(1) 使用荣阳的操作方式,两者没有实质性的区别

count的定义

Returns a count of the number of non-NULL values of expr in the rows retrieved by a SELECT statement. The result is a BIGINT value.

返回 SELECT 语句检索的行中 expr 的非 NULL 值的计数

也就是说COUNT这个聚合函数,对 SELECT 的结果集进行计数,但是需要参数不为 NULL

COUNT(*) is somewhat different in that it returns a count of the number of rows retrieved, whether or not they contain NULL values.

count() 不同,他不关心这个返回值是否为空都会计算他的count,因为 count(1) 中的 1 是恒真表达式,那么 count() 还是 count(1) 都是对所有的结果集进行 count,所以他们本质上没有什么区别

InnoDB的优化

这个地方 InnoDB 本身也做了一些优化,它会使用最小的二级索引来进行 count 的查询优化。如果没有二级索引才会选择聚簇索引,这样的设计单从 IO 的角度就节省了很多开销

count(column) 也是会遍历整张表,但是不同的是它会拿到 column 的值以后判断是否为空,然后再进行累加,那么如果针对主键需要解析内容,如果是二级索引所以需要再次根据主键获取内容,又是一次 IO 操作,所以 count(column) 的性能肯定不如前两者

count(*)=count(1)>count(primary key)>count(column)

count(*) 在查询上依赖于所有的数据集,我们也需要尽量的规避全量 count, 我们针对可预见的 count 查询做适当的缓存,可以是 Redis,也可以是独立的 MySQL count 表,当然无论是哪种方式我们都需要考虑一致性的问题。

文章中的其他衍生点

什么是mvcc

多版本并发控制,乐观锁的一种实现方式

MVCC主要是为Repeatable-Read(可重复读)事务隔离级别做的,大多数情况下代替了行锁,实现了对读的非阻塞,读不加锁,读写不冲突。但是每行记录都需要额外的存储空间,需要做更多的行维护和检查工作

MVCC 并发控制下的读事务一般使用时间戳或者事务 ID去标记当前读的数据库的状态。读写并存的时候,写操作会根据目前数据库的状态,创建一个新版本,并发的读则依旧访问旧版本的数据

MVCC就是用 同一份数据临时保留多版本的方式 的方式,实现并发控制,保证一个读事务永远不会被阻塞

innodb存储的最基本row中包含一些额外的存储信息,MVCC相关的有: DB_TRX_ID ,DB_ROLL_PTR,DB_ROW_ID

DB_TRX_ID 插入或更新行的最后一个事务的事务标识符

DB_ROLL_PTR 写入回滚段的撤消日志记录,找之前版本的数据就是通过这个

DB_ROW_ID 行标识.这个用于索引当中

DB_TRX_ID记录了行的创建的时间

在insert操作时, “创建时间”=DB_TRX_ID。“删除时间无”;

在update操作时,复制新增行的“创建时间”=DB_TRX_ID,删除时间无,旧数据行“创建时间”不变,删除时间=该事务DB_TRX_ID;

在delete操作时,相应数据行的“创建时间”不变,删除时间=该事务的DB_ROW_ID;

在select操作时,对两者都不修改,只读相应的数据

什么是最小二级索引

二级索引是每个叶子节点中包含了不是主键的索引列,紧接着就是主键列的这种索引

为什么二级索引需要两次索引?

二级索引的叶子节点保存的不是指向行的物理位置的指针,而是行的主键值,所以二级索引需要在查找一次聚簇索引。

叶子节点存储主键值的目的:

减少了当出现行移动或者数据分裂时二级索引的维护工作。使用主键值当作指针会让二级索引占用更多的空间,但换来在移动时无须更新二级索引中的主键指针。

什么是聚簇索引

InnoDB的聚簇索引实际上是在同一个结构中保存了B-Tree索引和数据行。 可以将聚簇索引理解为数据库中完整的一张数据表

每一个表都有一个聚簇索引,并且只有一个

聚簇索引的优点:

  • 将相关数据保存在一起
  • 数据访问速度更快
  • 使用覆盖索引扫描的查询可以直接使用页节点中的主键值

聚簇索引的缺点:

  • 插入速度依赖插入顺序
  • 使全表扫描速度变慢
  • 更新聚簇索引列(主键)的代价很高

如何保证数据一致性

分布式事务

什么是分布式事务? 如何实现分布式事务?

另起一片文章