MySQL中無(wú)過(guò)濾條件的count詳解
count(*)
實(shí)現(xiàn)
1、MyISAM:將表的總行數(shù)存放在磁盤(pán)上,針對(duì)無(wú)過(guò)濾條件的查詢可以直接返回
如果有過(guò)濾條件的count(*),MyISAM也不能很快返回
2、InnoDB:從存儲(chǔ)引擎一行行地讀出數(shù)據(jù),然后累加計(jì)數(shù)
由于MVCC,在同一時(shí)刻,InnoDB應(yīng)該返回多少行是不確定
樣例
假設(shè)表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) |
最后時(shí)刻三個(gè)會(huì)話同時(shí)查詢t的總行數(shù),拿到的結(jié)果卻是不同的
InnoDB默認(rèn)事務(wù)隔離級(jí)別是RR,通過(guò)MVCC實(shí)現(xiàn)
- 每個(gè)事務(wù)都需要判斷每一行記錄是否對(duì)自己可見(jiàn)
優(yōu)化
1、InnoDB是索引組織表
- 聚簇索引樹(shù):葉子節(jié)點(diǎn)是數(shù)據(jù)
- 二級(jí)索引樹(shù):葉子節(jié)點(diǎn)是主鍵值
2、二級(jí)索引樹(shù)占用的空間比聚簇索引樹(shù)小很多
3、優(yōu)化器會(huì)在保證邏輯正確的前提下,遍歷最小的索引樹(shù),盡量減少掃描的數(shù)據(jù)量
- 針對(duì)無(wú)過(guò)濾條件的count操作,無(wú)論遍歷哪一顆索引樹(shù),效果都是一樣的
- 優(yōu)化器會(huì)為count(*)選擇最優(yōu)的索引樹(shù)
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同樣通過(guò)采樣來(lái)估算(非常不精確),誤差能到40%~50%
維護(hù)計(jì)數(shù)
緩存
方案
- 用Redis來(lái)保存表的總行數(shù)(無(wú)過(guò)濾條件)
- 這個(gè)表每插入一行,Redis計(jì)數(shù)+1,每刪除一行,Redis計(jì)數(shù)-1
缺點(diǎn)
丟失更新
1、Redis可能會(huì)丟失更新
2、解決方案:Redis異常重啟后,到數(shù)據(jù)庫(kù)執(zhí)行一次count(*)
- 異常重啟并不常見(jiàn),這時(shí)全表掃描的成本是可以接受的
邏輯不精確 – 致命
1、場(chǎng)景:顯示操作記錄的總數(shù)和最近操作的100條記錄
2、Redis和MySQL是兩個(gè)不同的存儲(chǔ)系統(tǒng),不支持分布式事務(wù),因此無(wú)法拿到精確的一致性視圖
時(shí)序A
session B在T3時(shí)刻,查到的100行結(jié)果里面有最新插入的記錄,但Redis還沒(méi)有+1,邏輯不一致
時(shí)刻 | session A | session B |
---|---|---|
T1 | ||
T2 | 插入一行數(shù)據(jù)R; | |
T3 | 讀取Redis計(jì)數(shù); 查詢最近100條記錄; |
|
T4 | Redis計(jì)數(shù)+1; |
時(shí)序B
session B在T3時(shí)刻,查到的100行結(jié)果里面沒(méi)有最新插入的記錄,但Redis已經(jīng)+1,邏輯不一致
時(shí)刻 | session A | session B |
---|---|---|
T1 | ||
T2 | Redis計(jì)數(shù)+1; | |
T3 | 讀取Redis計(jì)數(shù); 查詢最近100條記錄; |
|
T4 | 插入一行數(shù)據(jù)R; |
數(shù)據(jù)庫(kù)
- 把計(jì)數(shù)值放到數(shù)據(jù)庫(kù)單獨(dú)的一張計(jì)數(shù)表C中
- 利用InnoDB的crash-safe的特性,解決了崩潰丟失的問(wèn)題
- 利用InnoDB的支持事務(wù)的特性,解決了一致性視圖的問(wèn)題
- session B在T3時(shí)刻,session A的事務(wù)還未提交,表C的計(jì)數(shù)值+1對(duì)自己不可見(jiàn),邏輯一致
時(shí)刻 | session A | session B |
---|---|---|
T1 | ||
T2 | BEGIN; 表C中的計(jì)數(shù)值+1; |
|
T3 | BEGIN; 讀表C計(jì)數(shù)值; 查詢最新100條記錄; COMMIT; |
|
T4 | 插入一行數(shù)據(jù)R; COMMIT; |
count的性能
語(yǔ)義
1、count()是一個(gè)聚合函數(shù),對(duì)于返回的結(jié)果集,一行一行地進(jìn)行判斷
如果count函數(shù)的參數(shù)值不是NULL,累計(jì)值+1,否則不加,最后返回累計(jì)值
2、count(字段F)
- 字段F有可能為NULL
- 表示返回滿足條件的結(jié)果集里字段F不為NULL的總數(shù)
3、count(主鍵ID)、count(1)、count(*)
- 不可能為NULL
- 表示返回滿足條件的結(jié)果集的總數(shù)
4、Server層要什么字段,InnoDB引擎就返回什么字段
- count(*)例外,不返回整行,只返回空行
性能對(duì)比
count(字段F)
1、如果字段F定義為不允許為NULL,一行行地從記錄里讀出這個(gè)字段,判斷通過(guò)后按行累加
- 通過(guò)表結(jié)構(gòu)判斷該字段是不可能為NULL
2、如果字段F定義為允許NULL,一行行地從記錄里讀出這個(gè)字段,判斷通過(guò)后按行累加
- 通過(guò)表結(jié)構(gòu)判斷該字段是有可能為NULL
- 判斷該字段值是否實(shí)際為NULL
3、如果字段F上沒(méi)有二級(jí)索引,只能遍歷整張表(聚簇索引)
4、由于InnoDB必須返回字段F,因此優(yōu)化器能做出的優(yōu)化決策將減少
- 例如不能選擇最優(yōu)的索引來(lái)遍歷
count(主鍵ID)
- InnoDB會(huì)遍歷整張表(聚簇索引),把每一行的id值取出來(lái),返回給Server層
- Server層拿到id后,判斷為不可能為NULL,然后按行累加
- 優(yōu)化器可能會(huì)選擇最優(yōu)的索引來(lái)遍歷
count(1)
- InnoDB引擎會(huì)遍歷整張表(聚簇索引),但不取值
- Server層對(duì)于返回的每一行,放一個(gè)數(shù)字1進(jìn)去,判斷是不可能為NULL,按行累加
- count(1)比count(主鍵ID)快,因?yàn)閏ount(主鍵ID)會(huì)涉及到兩部分操作
- 解析數(shù)據(jù)行
- 拷貝字段值
count(*)
- count(*)不會(huì)把所有值都取出來(lái),而是專門(mén)做了優(yōu)化,不取值,因?yàn)椤?』肯定不為NULL,按行累加
- 不取值:InnoDB返回一個(gè)空行,告訴Server層不是NULL,可以計(jì)數(shù)
效率排序
- count(字段F) < count(主鍵ID) < count(1) ≈ count(*)
- 盡量使用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)
無(wú)索引
user_ids上無(wú)索引,而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字段,因此不會(huì)遍歷邏輯結(jié)果等價(jià)的更優(yōu)選擇idx_create_time
- 如果選擇idx_create_time,并且返回serial_id字段,這意味著必須回表
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)
優(yōu)化器選擇了最優(yōu)的索引idx_create_time來(lái)遍歷,而非聚簇索引
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實(shí)戰(zhàn)45講》
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- MySQL 大表的count()優(yōu)化實(shí)現(xiàn)
- MySQL中聚合函數(shù)count的使用和性能優(yōu)化技巧
- 關(guān)于mysql中innodb的count優(yōu)化問(wèn)題分享
- 聊聊MySQL的COUNT(*)的性能
- 詳解 MySQL中count函數(shù)的正確使用方法
- 淺談MySQL 統(tǒng)計(jì)行數(shù)的 count
- mysql count提高方法總結(jié)
- MySQL中count(*)、count(1)和count(col)的區(qū)別匯總
- mySQL count多個(gè)表的數(shù)據(jù)實(shí)例詳解
- MySQL COUNT函數(shù)的使用與優(yōu)化
相關(guān)文章
Mysql查詢很慢卡在sending data的原因及解決思路講解
今天小編就為大家分享一篇關(guān)于Mysql查詢很慢卡在sending data的原因及解決思路講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-04-04mysql error 1130 hy000:Host''localhost''解決方案
本文將詳細(xì)提供mysql error 1130 hy000:Host'localhost'解決方案,需要的朋友可以參考下2012-11-11通過(guò)命令行導(dǎo)入到mysql數(shù)據(jù)庫(kù)時(shí)出現(xiàn)亂碼的解決方法
原因: 含有中文字符的mysql數(shù)據(jù)庫(kù)文件,通過(guò)命令行導(dǎo)入到mysql數(shù)據(jù)庫(kù)中容易出現(xiàn)亂碼。2014-07-07Mysql 5.6.37 winx64安裝雙版本mysql筆記記錄
機(jī)器上現(xiàn)在已經(jīng)存在5.0版本MySQL的情況下裝一個(gè)最新版的mysql,下文通過(guò)實(shí)例代碼給大家介紹Mysql 5.6.37 winx64安裝雙版本mysql筆記記錄,感興趣的朋友一起看看吧2017-07-07mysql 遠(yuǎn)程連接數(shù)據(jù)庫(kù)的方法集合
MySQL數(shù)據(jù)庫(kù)不允許從遠(yuǎn)程訪問(wèn)怎么辦?本文提供了三種解決方法:2008-03-03Mysql使用全文索引(FullText?index)的實(shí)例代碼
使用索引時(shí)數(shù)據(jù)庫(kù)性能優(yōu)化的必備技能之一,下面這篇文章主要給大家介紹了關(guān)于Mysql使用全文索引(FullText?index)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04