MySQL的count(*)執行慢的解決方案和不同count的比較


業務中經常需要獲取一個表的行數,但隨着數據表不斷增大,你會發現一條count(*)語句執行的效率越來越讓人難以忍受。

聊到這里,有必要介紹一下count的實現方式

  • 在MyISAM中,MyISAM直接記錄了表的行數,執行count(*)會直接返回這個數字,因此效率很高。
  • 在InnoDB中,它會先把數據逐行讀取出來,再累計計數,執行效率受數據量的影響。

那么為什么InnoDB不能向MyISAM一樣維護一個表行數呢?
主要的原因是InnoDB所支持事務的多版本並發控制(MVCC),即使同一時刻執行同一語句也可能返回不同結果,維護一個count並沒有什么意義。此外查詢語句如果包含Where,那么即使MyISAM引擎也是需要花時間查找計數的。

那么如何解決count語句的效率問題就變成了一個實際問題,解決這個問題的核心是使用緩存來減少執行查詢語句,對於不同類型的數據我通常會使用下面幾種不同的解決方案:

1.實時性不是那么高的數據

對於不需要實時獲取准確的數據,一個很自然的想法就是緩存+有效期。程序第一次獲取行數的時候先從緩存里面找,如果找不到再去執行count語句,然后把結果寫入帶有生命周期的緩存中。這個方案可以有效的規避count效率慢的問題,對於不同實效性要求的數據也可以很容易的設置不同的緩存周期,但面對要求實時准確的數據就無能為力了。

2.要求實時准確的行數

這一類數據的解決思路是,對數據表進行增刪操作時,同步對緩存進行操作,這樣就可以確保緩存中的行數永遠都是最新的。
這個解決方案也存其他在問題,比如我們使用redis緩存,如果某一時刻redis異常重啟,如果在這個過程中數據表有新增數據,那么redis中的緩存數就會與實際行數存在差異,當然可以在啟動redis時重新執行count語句來同步准確數字。
即使redis不是異常重啟,redis緩存的行數也可能與數據庫中的不對。設想用戶一個查詢行數的請求恰好在MySQL新增數據和redis緩存+1的中間,這時候用戶獲取的行數就會比真實的數據量少1。

3.絕對准確一致的數據

上面說了第2種解決方案其實也不是絕對准確的,那么對於要求絕對准確的數據該怎么處理?思路其實還是和緩存一樣,我們可以用MySQL單獨建一張表來維護count,相比於緩存,這種解決方案可以充分利用MySQL的事務,可以確保對於任意時刻count和真實數量保持一致,用戶每次查詢數量時訪問這張計數表即可。

再來聊聊count(*), count(id), count(1)的性能區別
對於count(*),優化器專門優化count(*)的語義為“取行數”,其他“顯而易見”的優化並沒有做

對於count(主鍵id)來說,InnoDB引擎會遍歷整張表,把每一行的id值都取出來,返回給server層。server層拿到id后,判斷是不可能為空的,就按行累加。

對於count(1)來說,InnoDB引擎遍歷整張表,但不取值。server層對於返回的每一行,放一個數字“1”進去,判斷是不可能為空的,按行累加。

單看這兩個用法的差別的話,你能對比出來,count(1)執行得要比count(主鍵id)快。因為從引擎返回id會涉及到解析數據行,以及拷貝字段值的操作。

對於count(字段)來說:

  • 如果這個“字段”是定義為not null的話,一行行地從記錄里面讀出這個字段,判斷不能為null,按行累加;
  • 如果這個“字段”定義允許為null,那么執行的時候,判斷到有可能是null,還要把值取出來再判斷一下,不是null才累加。

按照效率排序的話,count(字段)<count(主鍵id)<count(1)≈count(*),因此盡量使用count(*)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM