繼上一篇文章:
MySQL:查詢欄位數量多少對查詢效率的影響 👈
我們繼續來討論一下 count(*)、count(欄位)實現上的區別。注意我們這裡都使用 Innodb 做為存儲引擎,不討論其他引擎。因為了有了前面的討論,更容易看出它們的區別,這裡我們有如下注意點:
我們需要做到執行計劃一樣,這裡以全表掃描為例。實際上 count 很可能使用到覆蓋索引(Using index),本文主要討論它們實現的異同。
count(*) 和 count(欄位) 在結果上可能並不一致。比如 count(欄位),但是某些行本欄位為 NULL 的話那麼將不會統計行數,下面將會說明這種 NULL 判斷的位置。本文還是使用簡單的全表掃描來進行對比實現上的區別。首先我們要明確的是 count 使用的是一個 COUNT 計數器。
在示例中我們也可以看到兩個語句的結果實際上並不一致。mysql> show create table baguai_f \G
*************************** 1. row ***************************
Table: baguai_f
Create Table: CREATE TABLE `baguai_f` (
`id` int(11) DEFAULT NULL,
`a` varchar(20) DEFAULT NULL,
`b` varchar(20) DEFAULT NULL,
`c` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> select * from baguai_f ;
+-+-+-+-+
| id | a | b | c |
+-+-+-+-+
| 1 | g | g | NULL |
| 1 | g1 | g1 | g1 |
| 3 | g2 | g2 | g2 |
| 4 | g | g | NULL |
| 5 | g | g | NULL |
| 6 | g3 | g3 | g3 |
+-+-+-+-+
6 rows in set (0.00 sec)
mysql> desc select count(*) from baguai_f where b='g';
+----+---++--+-++-+----+-+-++---+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+---++--+-++-+----+-+-++---+
| 1 | SIMPLE | baguai_f | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 16.67 | Using where |
+----+---++--+-++-+----+-+-++---+
1 row in set, 1 warning (0.00 sec)
mysql> desc select count(c) from baguai_f where b='g';
+----+---++--+-++-+----+-+-++---+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+---++--+-++-+----+-+-++---+
| 1 | SIMPLE | baguai_f | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 16.67 | Using where |
+----+---++--+-++-+----+-+-++---+
1 row in set, 1 warning (0.00 sec)
mysql> select count(*) from baguai_f where b='g';
++
| count(*) |
++
| 3 |
++
1 row in set (0.00 sec)
mysql> select count(c) from baguai_f where b='g';
++
| count(c) |
++
| 0 |
++
1 row in set (0.00 sec)
這種不一致來自於 b='g' 的 c 列中都是 NULL 值,因此 count(c) 返回為 0。1. MySQL 層 構建 read_set
這裡構建的 read_set 實際上只會包含列b,即一個欄位。2. Innodb 層 構建模板
同理根據 read_set 構建的欄位模板中只會包含列b。3. Innodb 層 根據模板返回數據
斷點:row_sel_store_mysql_rec
查看模板數量:
(gdb) p prebuilt->n_template
$1 = 1
斷點:row_sel_field_store_in_mysql_format_func
查看模板對應的欄位:
(gdb) p field->name
$3 = {m_name = 0x7ffe7c99cf85 "b"}
顯然這裡只是將 b 列的值返回給了 MySQL層,這裡也很好理解,因為 b 列在 MySQL 層需要繼續做過濾操作。4. MySQL 層 過濾條件 b='g'
好了,當前返回給 MySQL 層的數據中只有 b 列的數據,然後施加 b='g' 這個條件進行過濾。5. MySQL 層 過濾後做一個 COUNT 計數操作
對於普通的 select 語句過濾後的數據就可以返回了,但是對於 count 這種操作,這裡做的是一個計數操作,其中行會對 count 欄位的 NULL 值進行判斷,當然這裡是 count(*) 也就不存在 NULL 值判斷了,下面是這段代碼:bool Item_sum_count::add()
{
if (aggr->arg_is_null(false))
return 0;
count++;
return 0;
}
END LOOP
最終我們只需要返回這個計數就可以了。下面是發送的數據,斷點可以設置在 Query_result_send::send_data 中。$22 = Item::SUM_FUNC_ITEM
(gdb) p ((Item*)(items)->first->info)->field_type()
$23 = MYSQL_TYPE_LONGLONG
(gdb) p ((Item*)(items)->first->info)->val_int()
$24 = 3
(gdb) p (items)->first->info
$26 = (void *) 0x7ffe7c006580
(gdb) p ((Item_sum_count*)$26)->count
$28 = 3
我們可以發送的數據實際就是這個計數器,最終值為 3。
三、示例中 count(c) 獲取數據流程的不同
實際上整個流程基本一致,但是區別在於:
構建的 read_set 不同,模板個數自然不同,因為需要 2 個欄位,即 b、c 兩個欄位,其中 b 列用於 where 條件過濾,而 b 列用於統計是否有 NULL 值,因此模板數量為 2,如下:(gdb) p prebuilt->n_template
$29 = 2
做 COUNT 計數器的時候會根據 c 列的 NULL 值做實際的過濾,操作只要是 NULL 則 count 計數不會增加 1,這個還是參考這段代碼:bool Item_sum_count::add()
{
if (aggr->arg_is_null(false)) //過濾NULL值
return 0;
count++;
return 0;
}
最終會調入函數 Field::is_null 進行 NULL 值判斷,斷點可以設置在這裡。示例中的語句 count(c) 返回為 0。現在我們很清楚了,這些數據什麼時候過濾掉的,總結如下:而 count(*) 則沒有第 3 步,這是一個不同。然後的不同點就是在返回的欄位上:
通過上面的分析,實際上效率沒有太大的差別,我覺得同樣執行計劃,同樣返回數據結果的前提下,可能 count(*) 的效率要略微高一點。最後推薦高鵬的專欄《深入理解 MySQL 主從原理 32 講》,想要透徹了解學習 MySQL 主從原理的朋友不容錯過。
作者微信:gp_22389860
診斷範圍支持:
Mycat 的故障診斷、源碼分析、性能優化
服務支持渠道:
技術交流群,進群後可提問
QQ群(669663113)
社區通道,郵件&電話
osc@actionsky.com
現場拜訪,線下實地,1天免費拜訪
關注「愛可生開源社區」公眾號,回復關鍵字「Mycat」,獲取活動詳情。
徵稿內容:
格式:.md/.doc/.txt
主題:MySQL、分布式中間件DBLE、數據傳輸組件DTLE相關技術內容
要求:原創且未發布過
獎勵:作者署名;200元京東E卡+社區周邊
投稿方式:
郵箱:osc@actionsky.com
格式:[投稿]姓名+文章標題
以附件形式發送,正文需註明姓名、手機號、微信號,以便小編及時聯繫
喜歡點「分享」,不行就「在看」