count函數(shù)是用來獲取表中滿足一定條件的記錄數(shù),常見用法有三種,count(*),count(1),count(field),這三種有什么區(qū)別?在性能上有何差異?本文將通過測試案例詳細(xì)介紹和分析。
原文地址:
mytecdb.com/blogDetail.php?id=81
三者有何區(qū)別:
- count(field)不包含字段值為NULL的記錄。
- count(*)包含NULL記錄。
- select(*)與select(1) 在InnoDB中性能沒有任何區(qū)別,處理方式相同。官方文檔描述如下:
InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.
1. 性能對比
通過案例來測試一下count(*),count(1),count(field)的性能差異,MySQL版本為5.7.19,測試表是一張sysbench生成的表,表名sbtest1,總記錄數(shù)2411645,如下:
CREATE TABLE sbtest1 (
id int(11) NOT NULL AUTO_INCREMENT,
k int(11) DEFAULT NULL,
c char(120) NOT NULL DEFAULT '',
pad char(60) NOT NULL DEFAULT '',
PRIMARY KEY (id),
KEY k_1 (k)
) ENGINE=InnoDB;
測試SQL語句:
select count(*) from sbtest1;
select count(1) from sbtest1;
select count(id) from sbtest1;
select count(k) from sbtest1;
select count(c) from sbtest1;
select count(pad) from sbtest1;
針對count(*)、count(1)和count(id),加了強(qiáng)制走主鍵的測試,如下:
select count(*) from sbtest1 force index(primary);
select count(1) from sbtest1 force index(primary);
select count(id) from sbtest1 force index(primary);
另外對不同的測試SQL,收集了profile,發(fā)現(xiàn)主要耗時(shí)都在Sending data這個(gè)階段,記錄Sending data值。
匯總測試結(jié)果:
| 類型 | 耗時(shí)(s) | 索引 | Sending data耗時(shí)(s) |
|---|---|---|---|
| count(*) | 0.47 | k_1 | 0.463624 |
| count(1) | 0.46 | k_1 | 0.463242 |
| count(id) | 0.52 | k_1 | 0.521618 |
| count(*)強(qiáng)制走主鍵 | 0.54 | primay key | 0.538737 |
| count(1)強(qiáng)制走主鍵 | 0.55 | primary key | 0.545007 |
| count(id)強(qiáng)制走主鍵 | 0.60 | primary key | 0.598975 |
| count(k) | 0.53 | k_1 | 0.529366 |
| count(c) | 0.81 | NULL | 0.813918 |
| count(pad) | 0.76 | NULL | 0.762040 |
結(jié)果分析:
- 從以上測試結(jié)果來看,count(*)和count(1)性能基本一樣,默認(rèn)走二級索引(k_1),性能最好,這也驗(yàn)證了count(*)和count(1)在InnoDB內(nèi)部處理方式一樣。
- count(id) 雖然也走二級索引(k_1),但是性能明顯低于count(*)和count(1),可能MySQL內(nèi)部在處理count(*)和count(1)時(shí)做了額外的優(yōu)化。
- 強(qiáng)制走主鍵索引時(shí),性能反而沒有走更小的二級索引好,InnoDB存儲引擎是索引組織表,行數(shù)據(jù)在主鍵索引的葉子節(jié)點(diǎn)上,走主鍵索引掃描時(shí),處理的數(shù)據(jù)量比二級索引更多,所以性能不及二級索引。
- count(c)和count(pad)沒有走索引,性能最差,但是明顯count(pad)比count(c)好,因?yàn)閜ad字段類型為char(60),小于字段c的char(120),盡管兩者性能墊底,但是字段小的性能相對更好些。
2. count(*)延伸
- 在5.7.18版本之前,InnoDB處理select count(*) 是通過掃描聚簇索引,來獲取總記錄數(shù)。
- 從5.7.18版本開始,InnoDB掃描一個(gè)最小的可用的二級索引來獲取總記錄數(shù),或者由SQL hint來告訴優(yōu)化器使用哪個(gè)索引。如果二級索引不存在,InnoDB將會掃描聚簇索引。
執(zhí)行select count(*)在大部分場景下性能都不會太好,尤其是表記錄數(shù)特別大的情況下,索引數(shù)據(jù)不在buffer pool里面,需要頻繁的讀磁盤,性能將更差。
3. count(*)優(yōu)化思路
- 一種優(yōu)化方法,是使用一個(gè)統(tǒng)計(jì)表來存儲表的記錄總數(shù),在執(zhí)行DML操作時(shí),同時(shí)更新該統(tǒng)計(jì)表。這種方法適用于更新較少,讀較多的場景,而對于高并發(fā)寫操作,性能有很大影響,因?yàn)樾枰l(fā)更新熱點(diǎn)記錄。
- 如果業(yè)務(wù)對count數(shù)量的精度沒有太大要求,可使用show table status中的行數(shù)作為近似值。