Session A | Session B | Session C |
select count(*) from t; | ||
insert into t (); | ||
begin; | ||
insert into t(); | ||
select count(*) from t; | select count(*) from t; | select count(*) from t; |
10000; | 結(jié)果是 10002 | 結(jié)果是 10001 |
對(duì)于 Session A 來(lái)說(shuō),Session B 未提交不可見(jiàn),Session C 提交了,但是在 Session A 啟動(dòng)后提交的,也不可見(jiàn)。所以是 10000.
而對(duì)于 Session B 而言,Session C 在啟動(dòng)之前提交,自己又插入了一條,所以結(jié)果是 10002.
其實(shí) InnoDB 在進(jìn)行 count(*) 操作時(shí),還是做了優(yōu)化的,在進(jìn)行 count(*) 操作時(shí),由于普通索引會(huì)保存主鍵的 id 值,所以會(huì)找到最小的那顆普通索引樹(shù)進(jìn)行查找,而不是去遍歷主鍵索引樹(shù)。
在保證邏輯正確的前提下,減少掃描的數(shù)據(jù)量,是數(shù)據(jù)庫(kù)系統(tǒng)設(shè)計(jì)的通用法則。
另外在使用 show table status 時(shí),也可以查詢出行數(shù),而且速度很快,但需要注意的是,該命令是通過(guò)索引統(tǒng)計(jì)的值來(lái)采樣估算的。官方文檔說(shuō)誤差可以有 40%-50%.
但如果我們真的需要實(shí)時(shí)的獲取的某個(gè)表的行數(shù),應(yīng)該怎么辦呢?
手動(dòng)保存表的數(shù)量
用緩存系統(tǒng)來(lái)保存計(jì)數(shù)
對(duì)于進(jìn)行更新的表,可能會(huì)想到用緩存系統(tǒng)來(lái)支持。比如 Redis 里來(lái)保存某個(gè)表總行數(shù)。
每次插入數(shù)據(jù)庫(kù)時(shí),Redis 計(jì)數(shù)加一,相反則減一,這樣看起來(lái)讀寫(xiě)操作都很快,但會(huì)存在一些問(wèn)題。
緩存系統(tǒng)會(huì)丟失更新:
對(duì)于 Redis 在內(nèi)存中的數(shù)據(jù),需要定期的同步到磁盤(pán)中,但對(duì)于 Redis 異常重啟,就沒(méi)有辦法了。比如在 Redis 中插入后,Redis 重啟,數(shù)據(jù)沒(méi)有持久化到硬盤(pán)。這時(shí)可以在重啟 Redis 后,從數(shù)據(jù)庫(kù)執(zhí)行下 count(*) 操作,然后更新到 Redis 中。一次全表掃描還是可行的。
邏輯不精確:
假設(shè)一個(gè)頁(yè)面中,需要顯示一張表的行數(shù),以及每一條數(shù)據(jù)。在實(shí)現(xiàn)時(shí),可以先從 Redis 取數(shù)量,然后從數(shù)據(jù)庫(kù)里取記錄。
但可能會(huì)出現(xiàn)這樣的情況:
Session A | Session B | |
插入一條數(shù)據(jù); | T1 | |
讀 Redis 計(jì)數(shù); | T2 | |
從數(shù)據(jù)庫(kù)中查記錄; | ||
Redis 計(jì)數(shù)加 1; | T3 |
對(duì)于 Session B 來(lái)說(shuō),在 T2 時(shí)刻,會(huì)發(fā)現(xiàn) Redis 的數(shù)量比數(shù)據(jù)庫(kù)少 1 條。
Session A | Session B | |
Redis 計(jì)數(shù)加 1; | T1 | |
讀 Redis 計(jì)數(shù); | T2 | |
從數(shù)據(jù)庫(kù)中查記錄; | ||
插入一條數(shù)據(jù); | T3 |
對(duì)于 Session B 來(lái)說(shuō),在 T2 時(shí)刻,會(huì)發(fā)現(xiàn) Redis 的數(shù)量比數(shù)據(jù)庫(kù)多 1 條。
其實(shí)產(chǎn)生問(wèn)題的原因就是因?yàn)?Redis 和數(shù)據(jù)庫(kù)查記錄沒(méi)有在同一個(gè)事務(wù)中。
用數(shù)據(jù)庫(kù)保存
由于 InnoDB 引擎的支持,MySQL 本身是支持事務(wù)的,所以將 Redis 的插入操作換成在數(shù)據(jù)庫(kù)的更新操作,就可以利用在RR級(jí)別下的事務(wù)特性,進(jìn)而保證數(shù)據(jù)的精確性。
而且還有一點(diǎn),由于 redo log 的支持,在 MySQL 發(fā)生異常時(shí),是可以保證 crash-safe。
不同 count 用法的執(zhí)行效率
count() 本身是一個(gè)聚合函數(shù),對(duì)于返回的結(jié)果集,一行行地判斷。如果參數(shù)不是 NULL 的話,會(huì)一直累加,最后返回結(jié)果。
所以 count(*), count(id), count(1) 表示都是返回滿足條件的結(jié)果集總行數(shù)。
而 count(字段),則表示滿足條件的數(shù)據(jù)行里,不為 NULL 的字段。
對(duì)于 count(id) 來(lái)說(shuō),InnoDB 會(huì)遍歷整張表,把每行 id 取出來(lái),給 server 層。Server 判斷 id 是否為空,然后累加。
對(duì)于 count(1) 來(lái)說(shuō),InnoDB 會(huì)遍歷整張表,但不取值。Server 層會(huì)自己放入 1,然后累加。
所以對(duì)于 count(1) 的執(zhí)行會(huì)比 count(*) 要快,少了解析數(shù)據(jù)行以及拷貝字段值的操作。
對(duì)于 count(字段) 來(lái)說(shuō),如果字段定義時(shí)是 not null, 會(huì)一行行讀出,并判斷不能為 null,然后累加。如果定義時(shí)可以為 null,執(zhí)行時(shí),需要將值去除,判斷不是 null 才累加。
count(*) 除外,專(zhuān)門(mén)做了優(yōu)化,不取值,直接按行累加,并且會(huì)找到最小的索引樹(shù)進(jìn)行計(jì)算。
總結(jié)
MySQL count() 函數(shù)的執(zhí)行效率和底層的數(shù)據(jù)引擎有關(guān)。MyISAM 不加 where 條件,查詢會(huì)很快,但不支持事務(wù)。InnoDB 支持事務(wù),由于 MVCC 的實(shí)現(xiàn),導(dǎo)致每次查詢都需要一行行的掃描,效率不高。
解決方法可以通過(guò)設(shè)計(jì)外部緩存如 Redis,保存記錄。但存在異常重啟和數(shù)據(jù)不準(zhǔn)確的情況。可以通過(guò)在 InnoDB 中新建一張表,保存記錄這樣的解決方案。
最后,InnoDB 對(duì) count(*) 做了獨(dú)立的優(yōu)化,而其他的 count 操作,則需要額外的操作。
以上就是淺談MySQL 統(tǒng)計(jì)行數(shù)的 count的詳細(xì)內(nèi)容,更多關(guān)于Mysql count的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
標(biāo)簽:北京 鷹潭 合肥 日照 臺(tái)灣 阜新 鎮(zhèn)江 貴州
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《淺談MySQL 統(tǒng)計(jì)行數(shù)的 count》,本文關(guān)鍵詞 淺談,MySQL,統(tǒng)計(jì),行數(shù),的,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。