count(*) 的實現方式
你首先要明確的是肛走,在不同的 MySQL 引擎中焚志,count(*) 有不同的實現方式。
MyISAM 引擎把一個表的總行數存在了磁盤上胆筒,因此執(zhí)行 count(*) 的時候會直接返回這個數邮破,效率很高;
而 InnoDB 引擎就麻煩了仆救,它執(zhí)行 count(*) 的時候抒和,需要把數據一行一行地從引擎里面讀出來,然后累積計數彤蔽。
這里需要注意的是摧莽,我們在這篇文章里討論的是沒有過濾條件的 count(*),如果加了 where 條件的話顿痪,MyISAM 表也是不能返回得這么快的镊辕。
好了 下面開始我們今天博文的正式內容:
在 select count(?) from t 這樣的查詢語句里面,count(*)员魏、count(主鍵 id)丑蛤、count(字段) 和 count(1) 等不同用法的性能,有哪些差別撕阎。今天談到了 count(*) 的性能問題受裹,我就借此機會和你詳細說明一下這幾種用法的性能差別。
需要注意的是,下面的討論還是基于 InnoDB 引擎的
這里棉饶,首先你要弄清楚 count() 的語義厦章。count() 是一個聚合函數,對于返回的結果集照藻,一行行地判斷袜啃,如果 count 函數的參數不是 NULL,累計值就加 1幸缕,否則不加群发。最后返回累計值。
所以发乔,count(*)熟妓、count(主鍵 id) 和 count(1) 都表示返回滿足條件的結果集的總行數;而 count(字段)栏尚,則表示返回滿足條件的數據行里面起愈,參數“字段”不為 NULL 的總個數。
至于分析性能差別的時候译仗,你可以記住這么幾個原則:
1抬虽、server 層要什么就給什么;
2纵菌、InnoDB 只給必要的值阐污;
3、現在的優(yōu)化器只優(yōu)化了 count(*) 的語義為“取行數”产艾,其他“顯而易見”的優(yōu)化并沒有做疤剑。
對于 count(主鍵 id) 來說,InnoDB 引擎會遍歷整張表闷堡,把每一行的 id 值都取出來隘膘,返回給 server 層。server 層拿到 id 后杠览,判斷是不可能為空的弯菊,就按行累加。
對于 count(1) 來說踱阿,InnoDB 引擎遍歷整張表管钳,但不取值。server 層對于返回的每一行软舌,放一個數字“1”進去才漆,判斷是不可能為空的,按行累加佛点。
單看這兩個用法的差別的話醇滥,你能對比出來黎比,count(1) 執(zhí)行得要比 count(主鍵 id) 快。因為從引擎返回 id 會涉及到解析數據行鸳玩,以及拷貝字段值的操作阅虫。
對于 count(字段) 來說
1.如果這個“字段”是定義為 not null 的話,一行行地從記錄里面讀出這個字段不跟,判斷不能為 null颓帝,按行累加;
2.如果這個“字段”定義允許為 null窝革,那么執(zhí)行的時候购城,判斷到有可能是 null,還要把值取出來再判斷一下虐译,不是 null 才累加工猜。
也就是前面的第一條原則,server 層要什么字段菱蔬,InnoDB 就返回什么字段。
但是 count(*) 是例外史侣,并不會把全部字段取出來拴泌,而是專門做了優(yōu)化,不取值惊橱。count(*) 肯定不是 null蚪腐,按行累加。
看到這里税朴,你一定會說回季,優(yōu)化器就不能自己判斷一下嗎,主鍵 id 肯定非空啊正林,為什么不能按照 count(*) 來處理泡一,多么簡單的優(yōu)化啊。
當然觅廓,MySQL 專門針對這個語句進行優(yōu)化鼻忠,也不是不可以。但是這種需要專門優(yōu)化的情況太多了杈绸,而且 MySQL 已經優(yōu)化過 count(*) 了帖蔓,你直接使用這種用法就可以了。
所以結論是:按照效率排序的話瞳脓,count(字段)<count(主鍵 id)<count(1)≈count(*)塑娇,所以我建議你,盡量使用 count(*)劫侧。