關於 MySQL 百萬數據量的 count(*) 查詢如何優化?

明確需求

對這個問題有興趣是源於一次開發中遇到要統計人數的需求。類似於“得到”專欄的訂閱數。

clipboard.png

但是我的數據量比這個大很多,而對數據的準確性要求就不那麼高。所以首先要明確需求。其他答案有的說了用緩存,有的答案對比了count(*)、count(1)的區別,都很好,但是我認爲還是要看一下題主的場景。我根據我實際開發的經驗總結如下幾個方面,FYI。

clipboard.png

數據量大/準確性要求低/請求量大

  1. 這種場景一般是C端產品,比如上面說的得到APP的訂閱數目,如果對一致性要求不高,可以直接在內存中使用緩存,用guava在內存中做一個緩存定時刷新即可,百萬量級count(*)有緩存的頻率還不至於有啥性能問題;
  2. 但是內存內緩存有一個問題就是不同服務器之間的緩存數量是不一致的,可以考慮用redis作爲計數,一般這種場景是大多數同學遇到的,簡單粗暴搞定即可;
  3. 用show table status。這個建議還是不要用了,翻了下mysql 的doc,40%的誤差概率,碰上就有點大了呀。
TABLE_ROWS
The number of rows. Some storage engines, such as MyISAM, store the exact count. For other storage engines, such as InnoDB, this value is an approximation, and may vary from the actual value by as much as 40% to 50%. In such cases, use SELECT COUNT(*) to obtain an accurate count.

數據量大/準確性要求高/請求量一般

這種場景一般出現在賬務上,比如有多少人打款。而且估計DAU在億級別的公司可能纔會遇到。這裏最關鍵的問題還是一致性的要求。在併發系統中,看看我們用redis,我們看看會出現什麼樣的一致性問題:

時間       A processor         B processor
T1         插入數據
T2                             1.redis#get計數器;2. 查詢最新的N條數據
T3         redis#incr

在T2的時間點的時候會出現數據不一致,B看到的是數據已經更新,但是數據庫還沒更新。我們就在想,如果放到一個事務裏面,就可以完美解決這個問題了呀。由於事務,innoDB不支持像MyISAM準確計數,解鈴還須繫鈴人,所以我們建一個計數表(count_table)+事務,解這個問題了。

時間         會話A                            會話B
T1         begin;
           在計數表中插入一條數據;
T2                                          begin;
                                            1. 讀count_table;
                                            2. 查詢最新的N條數據
                                            commit;
T3         更新conut_table;
           commit;

在T1的時候,如果採用Mysql默認的事務隔離級別:讀提交。因爲T1事務還沒有提交,所以插入的數據,B是讀不到的,所以從邏輯上來說是一致的。

數據量大/準確性要求高/請求量特別高

抱歉,沒遇到過。如果你覺得你遇到了,你的架構需要你重新design and review,相信我。

帶條件count(*)

很多時候我們的業務場景不是數據量多,而是條件複雜。這其實就是一個查詢優化的問題了,和是不是count(*)沒有關係,那麼有以下兩招常用,這個得具體問題具體分析了。比如時間維度可以加一個索引來優化;

select * from table_name where a = x and b = x;
  • 加索引
  • 業務拆分

count性能比較

  • count(primary key)。遍歷整個表,把主鍵值拿出來,累加;
  • count(1)。遍歷整個表,但是不取值,累加;
  • count(非空字段)。遍歷整個表,讀出這個字段,累加;
  • count(可以爲空的字段)。遍歷整個表,讀出這個字段,判斷不爲null累加;
  • count(*)。遍歷整個表,做了優化,不取值,累加。

結合mysql的一些索引查詢知識,我們可以大致得出如下結論。

clipboard.png

建議直接使用count(*)。

Leetcode名企之路

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章