導讀
- 在開發中一定會用到統計一張表的行數,比如一個交易系統,老板會讓你每天生成一個報表,這些統計資訊少不了 sql 中的count函式,
- 但是隨著記錄越來越多,查詢的速度會越來越慢,為什么會這樣呢?Mysql內部到底是怎么處理的?
- 今天這篇文章將從Mysql內部對于
count函式是怎樣處理的? - 本文首發于作者微信公眾號【碼猿技術專欄】Mysql性能優化:為什么你的count(*)這么慢?,原創不易,喜歡的請支持一下,謝謝!!!
count的實作方式
- 在Mysql中的不同的存盤引擎對
count函式有不同的實作方式, MyISAM引擎把一個表的總行數存在了磁盤上,因此執行count(*)的時候會直接回傳這個數,效率很高(沒有where查詢條件),InnoDB引擎并沒有直接將總數存在磁盤上,在執行count(*)函式的時候需要一行一行的將資料讀出來,然后累計總數,
為什么InnoDB不將總數存起來?
-
說到InnoDB相信讀者總會想到其支持事務的特性,事務具有隔離性,如果將總數存起來,怎么保證各個事務之間的總數的一致性呢?不明白的看圖

-
事務A和事務B中的count(*)的執行結果是不同的,因此InnoDB引擎在每個事務中回傳多少行是不確定的,只能一行一行的讀出來用來判斷總數,
如何提升count效率
- 在
InnoDB對于如何提升count(*)的查詢效率,網上有多種解決辦法,這里主要介紹三種,并分析可行性,
show table status
show table status這個命令能夠很快的查詢出資料庫中每個表的行數,但是真的能夠替代count(*)嗎?- 答案是不能,原因很簡單,這個命令統計出來的值是一個「估值」,因此是不準確的,官方檔案說誤差大概在
40%-50%, - 因此這種方法直接pass,不準確還用它干嘛,
快取系統存盤總數
-
這種方法也是最容易想到的,增加一行就
+1,洗掉一行就-1,并且快取系統讀取也是很快,既簡單又方便的為什么不用? -
快取系統和Mysql是兩個系統,比如
redis和Mysql這兩個是典型的比較,兩個系統最難的就是在高并發下無法保證資料的一致性,

-
通過上面兩張圖,無論是
redis計數+1還是insert into user先執行,最終都會導致資料在邏輯上的不一致,第一張圖會出現redis計數少了,第二張圖雖然計數正確了但是并沒有查詢出插入的那一行資料, -
在并發系統里面,我們是無法精確控制不同執行緒的執行時刻的,因為存在圖中的這種操作序列,所以,我們說即使Redis正常作業,這個計數值還是邏輯上不精確的,
在資料庫保存計數
-
通過快取系統保存的分析得知了使用快取無法保證資料在邏輯上的一致性,因此我們想到了直接使用資料庫來保存,有了「事務」的支持,也就保證了資料的一致性了,
-
如何使用呢?很簡單,直接將計數保存在一張表中
(table_name,total), -
至于執行的邏輯只需要將快取系統中
redis計數+1改成total欄位+1即可,如下圖:
-
由于在同一個事務中,保證了資料在邏輯上的一致性,
不同count的用法
count()是一個聚合函式,對于回傳的結果集,一行行地判斷,如果count函式的引數不是NULL,累計值就加1,否則不加,最后回傳累計值,count的用法有多種,分別是count(*)、count(欄位)、count(1)、count(主鍵id),那么多種用法,到底有什么差別呢?當然,「前提是沒有where條件陳述句」,count(id):InnoDB引擎會遍歷整張表,把每一行的id值都取出來,回傳給server層,server層拿到id后,判斷是不可能為空的,就按行累加,count(1):InnoDB引擎遍歷整張表,但不取值,server層對于回傳的每一行,放一個數字1進去,判斷是不可能為空的,按行累加,count(欄位):count(*):不會把全部欄位取出來,而是專門做了優化,不取值,count(*)肯定不是null,按行累加,- 如果這個“欄位”是定義為
not null的話,一行行地從記錄里面讀出這個欄位,判斷不能為null,按行累加; - 如果這個欄位定義允許為
null,那么執行的時候,判斷到有可能是null,還要把值取出來再判斷一下,不是null才累加,
- 如果這個“欄位”是定義為
- 所以結論很簡單:「按照效率排序的話,
count(欄位)<count(主鍵id)<count(1)≈count(*),所以建議讀者,盡量使用count(*),」 - 「注意」:這里肯定有人會問,
count(id)不是走的索引嗎,為什么查詢效率和其他的差不多呢?陳某在這里解釋一下,雖然走的索引,但是還是要一行一行的掃描才能統計出來總數,
總結
MyISAM表雖然count(*)很快,但是不支持事務;show table status命令雖然回傳很快,但是不準確;InnoDB直接count(*)會遍歷全表(沒有where條件),雖然結果準確,但會導致性能問題,- 快取系統的存盤計數雖然簡單效率高,但是無法保證資料的一致性,
- 資料庫保存計數很簡單,也能保證資料的一致性,建議使用,
- 「思考題,讀者留言區討論」:在系統高并發的情況下,使用資料庫保存計數,是先
更新計數+1,還是先插入資料,即是先update total+=1還是先insert into,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/61073.html
標籤:MySQL
上一篇:MySQL基礎陳述句
