count(*)
实现
1、myisam:将表的总行数存放在磁盘上,针对无过滤条件的查询可以直接返回
如果有过滤条件的count(*),myisam也不能很快返回
2、innodb:从存储引擎一行行地读出数据,然后累加计数
由于mvcc,在同一时刻,innodb应该返回多少行是不确定
样例
假设表t有10000条记录
session a | session b | session c |
---|---|---|
begin; | ||
select count(*) from t;(返回10000) | ||
insert into t;(插入一行) | ||
begin; | ||
insert into t(插入一行); | ||
select count(*) from t;(返回10000) | select count(*) from t;(返回10002) | select count(*) from t;(返回10001) |
最后时刻三个会话同时查询t的总行数,拿到的结果却是不同的
innodb默认事务隔离级别是rr,通过mvcc实现
优化
1、innodb是索引组织表
2、二级索引树占用的空间比聚簇索引树小很多
3、优化器会在保证逻辑正确的前提下,遍历最小的索引树,尽量减少扫描的数据量
show table status
mysql> show table status\g; *************************** 1. row *************************** name: t engine: innodb version: 10 row_format: dynamic rows: 100256 avg_row_length: 47 data_length: 4734976 max_data_length: 0 index_length: 5275648 data_free: 0 auto_increment: null create_time: 2019-02-01 17:49:07 update_time: null check_time: null collation: utf8_general_ci checksum: null create_options: comment:
show table status同样通过采样来估算(非常不精确),误差能到40%~50%
维护计数
缓存
方案
缺点
丢失更新
1、redis可能会丢失更新
2、解决方案:redis异常重启后,到数据库执行一次count(*)
逻辑不精确 – 致命
1、场景:显示操作记录的总数和最近操作的100条记录
2、redis和mysql是两个不同的存储系统,不支持分布式事务,因此无法拿到精确的一致性视图
时序a
session b在t3时刻,查到的100行结果里面有最新插入的记录,但redis还没有+1,逻辑不一致
时刻 | session a | session b |
---|---|---|
t1 | ||
t2 | 插入一行数据r; | |
t3 | 读取redis计数; 查询最近100条记录; |
|
t4 | redis计数+1; |
时序b
session b在t3时刻,查到的100行结果里面没有最新插入的记录,但redis已经+1,逻辑不一致
时刻 | session a | session b |
---|---|---|
t1 | ||
t2 | redis计数+1; | |
t3 | 读取redis计数; 查询最近100条记录; |
|
t4 | 插入一行数据r; |
数据库
时刻 | session a | session b |
---|---|---|
t1 | ||
t2 | begin; 表c中的计数值+1; |
|
t3 | begin; 读表c计数值; 查询最新100条记录; commit; |
|
t4 | 插入一行数据r; commit; |
count的性能
语义
1、count()是一个聚合函数,对于返回的结果集,一行一行地进行判断
如果count函数的参数值不是null,累计值+1,否则不加,最后返回累计值
2、count(字段f)
3、count(主键id)、count(1)、count(*)
4、server层要什么字段,innodb引擎就返回什么字段
性能对比
count(字段f)
1、如果字段f定义为不允许为null,一行行地从记录里读出这个字段,判断通过后按行累加
2、如果字段f定义为允许null,一行行地从记录里读出这个字段,判断通过后按行累加
3、如果字段f上没有二级索引,只能遍历整张表(聚簇索引)
4、由于innodb必须返回字段f,因此优化器能做出的优化决策将减少
count(主键id)
count(1)
count(*)
效率排序
样例
mysql> show create table prop_action_batch_reward\g; *************************** 1. row *************************** table: prop_action_batch_reward create table: create table `prop_action_batch_reward` ( `id` bigint(20) not null, `source` int(11) default null, `serial_id` bigint(20) not null, `create_time` datetime not null default current_timestamp, `user_ids` mediumtext, `serial_index` tinyint(4) default '0', primary key (`id`), unique key `uniq_serial_id_source_index` (`serial_id`,`source`,`serial_index`), key `idx_create_time` (`create_time`) ) engine=innodb default charset=utf8
count(字段f)
无索引
user_ids上无索引,而innodb又必须返回user_ids字段,只能遍历聚簇索引
mysql> explain select count(user_ids) from prop_action_batch_reward; +----+-------------+--------------------------+------+---------------+------+---------+------+----------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | extra | +----+-------------+--------------------------+------+---------------+------+---------+------+----------+-------+ | 1 | simple | prop_action_batch_reward | all | null | null | null | null | 16435876 | null | +----+-------------+--------------------------+------+---------------+------+---------+------+----------+-------+ mysql> select count(user_ids) from prop_action_batch_reward; +-----------------+ | count(user_ids) | +-----------------+ | 17689788 | +-----------------+ 1 row in set (10.93 sec)
有索引
1、serial_id上有索引,可以遍历uniq_serial_id_source_index
2、但由于innodb必须返回serial_id字段,因此不会遍历逻辑结果等价的更优选择idx_create_time
mysql> explain select count(serial_id) from prop_action_batch_reward; +----+-------------+--------------------------+-------+---------------+-----------------------------+---------+------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | extra | +----+-------------+--------------------------+-------+---------------+-----------------------------+---------+------+----------+-------------+ | 1 | simple | prop_action_batch_reward | index | null | uniq_serial_id_source_index | 15 | null | 16434890 | using index | +----+-------------+--------------------------+-------+---------------+-----------------------------+---------+------+----------+-------------+ mysql> select count(serial_id) from prop_action_batch_reward; +------------------+ | count(serial_id) | +------------------+ | 17705069 | +------------------+ 1 row in set (5.04 sec)
count(主键id)
优化器选择了最优的索引idx_create_time来遍历,而非聚簇索引
mysql> explain select count(id) from prop_action_batch_reward; +----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | extra | +----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+ | 1 | simple | prop_action_batch_reward | index | null | idx_create_time | 5 | null | 16436797 | using index | +----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+ mysql> select count(id) from prop_action_batch_reward; +-----------+ | count(id) | +-----------+ | 17705383 | +-----------+ 1 row in set (4.54 sec)
count(1)
mysql> explain select count(1) from prop_action_batch_reward; +----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | extra | +----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+ | 1 | simple | prop_action_batch_reward | index | null | idx_create_time | 5 | null | 16437220 | using index | +----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+ mysql> select count(1) from prop_action_batch_reward; +----------+ | count(1) | +----------+ | 17705808 | +----------+ 1 row in set (4.12 sec)
count(*)
mysql> explain select count(*) from prop_action_batch_reward; +----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | extra | +----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+ | 1 | simple | prop_action_batch_reward | index | null | idx_create_time | 5 | null | 16437518 | using index | +----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+ mysql> select count(*) from prop_action_batch_reward; +----------+ | count(*) | +----------+ | 17706074 | +----------+ 1 row in set (4.06 sec)
参考资料
《mysql实战45讲》
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。
如对本文有疑问, 点击进行留言回复!!
服务器Centos部署MySql并连接Navicat过程详解
网友评论