select count(*)
應(yīng)該是一個比較常用的語句,用來統(tǒng)計記錄行數(shù)耀找。
但是翔悠,慢慢地你會發(fā)現(xiàn),這個語句越來越慢了野芒,為什么呢蓄愁?
count(*) 的實現(xiàn)方式
首先,我們來看下它的實現(xiàn)方式狞悲。
MySQL 中撮抓,不同的存儲引擎,count(*)
的實現(xiàn)方式是不同的摇锋。
1丹拯、MyISAM 引擎,比較簡單粗暴荸恕,直接將表的總行數(shù)存儲在磁盤上乖酬,因此效率很高;
2融求、InnoDB 引擎中咬像,執(zhí)行時,需要一行行的把數(shù)據(jù)查出來,然后累加县昂;
為啥 MyISAM 就可以這樣做呢肮柜?因為它不支持事務(wù)啊,不用擔(dān)心數(shù)據(jù)不一致的問題七芭。
而 InnoDB 就不一樣了素挽。
由于 MVCC 的存在,InnoDB 在當(dāng)前執(zhí)行環(huán)境下狸驳,對一共有多少數(shù)據(jù)行是不確定的预明,比如:
假設(shè),表 t 中有 1000 條數(shù)據(jù)耙箍,有下面三個用戶并行的會話:
1撰糠、A 啟動事務(wù),查詢表的總行數(shù)辩昆;
2阅酪、C 直接插入一條數(shù)據(jù),然后查詢總行數(shù)汁针;
3术辐、B 啟動事務(wù),插入一條數(shù)據(jù)施无,然后查詢總行數(shù)辉词;
4、C 查詢總行數(shù)猾骡;
注意瑞躺,上面啟動的事務(wù)都沒有提交。
A兴想、B幢哨、C 查詢的結(jié)果都不相同。
B 讀到的是 1002嫂便,是因為可重復(fù)讀隔離級別的存在捞镰,而 C 未開啟事務(wù),因此無法看到別的事務(wù)的更新毙替;
綜上曼振,InnoDB 引擎中,在每一個會話中蔚龙,都需要逐行讀取數(shù)據(jù),然后計數(shù)返回總行數(shù)映胁。
InnoDB 對 count(*) 的優(yōu)化
InnoDB 中木羹,主鍵索引存儲的是數(shù)據(jù),輔助索引存儲的只是主鍵值。
因此坑填,輔助索引比主鍵索引小得多抛人,輕量得多。
這種情況下脐瑰,InnoDB 在執(zhí)行 count(*)
時妖枚,就會判斷使用哪個索引,會選擇最小的樹來進(jìn)行遍歷苍在。
在保證邏輯正確的前提下绝页,盡量減少掃描的數(shù)據(jù)量,是數(shù)據(jù)庫系統(tǒng)設(shè)計的通用法則之一寂恬。
小結(jié)
1续誉、由于 MyISAM 引擎不需要支持事務(wù),因此可以快速返回 count(*)
初肉;
2酷鸦、show table status 命令雖然返回很快,但是不準(zhǔn)確牙咏;
3臼隔、InnoDB 執(zhí)行 count(*)
時會遍歷全表,因此性能較差妄壶;
count(*)摔握、count(1)、count(主鍵)盯拱、count(字段)的區(qū)別
以下盒发,基于 InnoDB。
含義區(qū)別
count()
是一個聚合函數(shù)狡逢,對于返回的結(jié)果集宁舰,會逐行判斷,若返回的不是 NULL奢浑,就會加 1蛮艰,否則不加。
因此雀彼,count(*)
壤蚜、count(主鍵 id)
和 count(1)
都表示返回滿足條件的結(jié)果集的總行數(shù);而 count(字段)
徊哑,則表示返回滿足條件的數(shù)據(jù)行里面袜刷,參數(shù)“字段”不為 NULL 的總個數(shù)。
性能區(qū)別
分析性能莺丑,考慮以下幾個原則:
1著蟹、server 層要什么就會返回什么墩蔓;
2、InnoDB 只返回必要的值萧豆;
3奸披、優(yōu)化器只優(yōu)化了 count(*)
對于 count(主鍵id)
,InnoDB 會遍歷全表涮雷,取每行的主鍵 id阵面,返回給 server 層,server 層拿到數(shù)據(jù)后洪鸭,進(jìn)行判斷累加样刷。
對于 count(1)
,InnoDB 仍遍歷全表卿嘲,但是不取值颂斜,server 層對返回的每一行數(shù)據(jù)新增一個 1,然后進(jìn)行判斷累加拾枣;
因此沃疮,count(1)
要更快些,因為無需取值梅肤。從引擎返回 id 會涉及到解析數(shù)據(jù)行司蔬,以及拷貝字段值的操作。
對于 count(字段)
:
1姨蝴、如果這個“字段”是定義為 not null 的話俊啼,一行行地從記錄里面讀出這個字段,判斷不能為 null左医,按行累加授帕;2、如果這個“字段”定義允許為 null浮梢,那么執(zhí)行的時候跛十,判斷到有可能是 null,還要把值取出來再判斷一下秕硝,不是 null 才累加芥映。
但是 count(*)
是例外,并不會把全部字段取出來远豺,而是專門做了優(yōu)化奈偏,不取值。count(*)
肯定不是 null躯护,按行累加惊来。
結(jié)論:按照效率排序的話,count(字段)<count(主鍵 id)<count(1)≈count(*)
棺滞,所以我建議你唁盏,盡量使用 count(*)
内狸。
關(guān)注本公眾號厘擂,后臺回復(fù)「2018」即可獲取傳智播客 2018 最新 Python 和 Java 教程。
公眾號提供CSDN資源免費(fèi)下載服務(wù)锰瘸!