count(*) 優化

萌新J發表於2021-01-04

幾種獲取記錄數的方法

count(*):  MySQL 優化過,掃描的行數小於總記錄數。執行效率高。

count(1):  遍歷所有記錄,不取值,對每行嘗試新增一個 “1” 列,如果不為 null,就計入累加(引擎層)。

count(主鍵):  遍歷所有記錄,並把每個記錄的 id 取出返回 Server 層判斷,將不為 null 的計入累加。

count(欄位):  遍歷所有記錄,並把每個記錄的欄位值取出返回 Server 層判斷,將不為 null 的計入累加。

效率排序:  count(*) ≈ count(1) > count(主鍵) > count(欄位)

除此之外,還可以通過 " show table status like '表名' " 檢視 'TABLE_ROW' 引數來獲取系統內部通過取樣估算的記錄數,但誤差會達到 40% -- 50%。

 

優化

使用快取

通過上面的分析可以知道通過 count(*) 來獲取計數已經是效率最高的一種方式了, 但是如果效率還是低呢?首先效率低肯定是執行計數操作的併發量太高所導致的。而應對查詢操作最常見的優化方式就是使用快取,但是使用快取適用於查多寫少的場景,不過因為計數不會因為修改操作而改變,只會被增刪操作所改變,所以在增刪操作少的場景也是可以使用的。但是還有另外一個問題,那就是獲取計數的業務和計數自增自減的操作不能保證原子性。這樣導致查出的結果可能並不準確。

比如:

有一個頁面,要顯示操作記錄的總數,同時還要顯示最近操作的 100 條記錄。那麼,這個頁面的邏輯就需要先到 Redis 裡面取出計數,再到資料表裡面取資料記錄。

一種是,查到的 100 行結果裡面有最新插入記錄,而 Redis 的計數裡還沒加 1;

另一種是,查到的 100 行結果裡沒有最新插入的記錄,而 Redis 的計數裡已經加了 1。

那麼第一種情況就會導致查詢的計數和記錄對不上,得到的結果混亂。

 

資料庫

如果將計數結果單獨建立一張表來儲存,搭配 MySQL 的可重複讀隔離級別,就可以實現資料讀取的 "原子性" ,並且效率也會很高。

 在會話B讀取計數時就會因為會話A還未提交,所以不會讀取到會話A執行過的操作。

相關文章