技術分享 | MySQL:count(*)、count(欄位) 實現上區別

2021-12-24 愛可生開源社區
文章末尾有他著作的《深入理解 MySQL 主從原理 32 講》,深入透徹理解 MySQL 主從,GTID 相關技術知識。

繼上一篇文章:

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

格式:[投稿]姓名+文章標題

以附件形式發送,正文需註明姓名、手機號、微信號,以便小編及時聯繫

喜歡點「分享」,不行就「在看」

相關焦點

  • mysql count(*)count(1)count(欄位)區別
    在mysql中查詢某個表多少條,一般是用count函數來查詢,但是count函數支持*,1,欄位等。
  • MySQL中count(欄位) ,count(主鍵 id) ,count(1)和count(*)的區別
    所以,count(*)、count(1)和count(主鍵 id) 都表示返回滿足條件的結果集的總行數;而 count(欄位),則表示返回滿足條件的數據行裡面,參數「欄位」不為 NULL 的總個數。至於分析性能差別的時候,記住這麼幾個原則:count(可空欄位)掃描全表,讀到server層,判斷欄位可空,拿出該欄位所有值,判斷每一個值是否為空,不為空則累加
  • mysql中count(*)是最慢的嗎?
    count如何實現的:在mysql中,當數據表數據比較少的時候,使用count可以很快進行返回需要統計的數據行數,當數量比較大的時候,count的返回結果的速度就變慢的很多,出現這種情況時,使用的mysql存儲引擎大概率是InnoDB。因為在MyISAM引擎中,對每個表的總行數都會進行記錄,並存在磁碟上。
  • mysql大表中count()的用法以及mysql中count()的優化
    本篇文章給大家帶來的內容是關於mysql大表中count()的用法以及mysql中count()的優化,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。一個單表中包含有6000w+的數據,然而你又不能拆分.需要分別統計表中有多少數據,A產品有多少,B產品有多少這幾個數據.
  • MySQL:SELECT COUNT 小結
    (1) 、 select count(*),和具體的select count(欄位)的不同寫法,本著分析的目的在會議室討論了起來,那這幾種寫法究竟孰優孰劣呢,我們一起來看一下。討論歸納先來看看MySQL官方對SELECT COUNT的定義:傳送門:https://dev.mysql.com/doc/refman/5.6/en/aggregate-functions.html#function_count
  • MySQL count(*),count(1),count(field)區別、性能差異及優化建議
    MySQL count函數是用來獲取表中滿足一定條件的記錄數,常見用法有三種,count(*),count(1),count(field),這三種有什麼區別?在性能上有何差異?本文將通過測試案例詳細介紹和分析。
  • MySQL系列:count(*) 性能問題
    1count( * )的實現方式首先要明確的是,不同的引擎中count( * )有不同的實現方式。這和InnoDB的事務設計有關係,可重複讀是它默認的隔離級別,在代碼上就是通過 MVCC 來實現的。每一行記錄都要判斷自己是否對這個會話可見,因此對於count( * )請求來說,innodb 只好把數據一行一行地讀出依次判斷,可見的行才能夠用於計算「基於這個查詢」的表的總行數。
  • 卓象程式設計師:MySQL中count的使用
    select count(*)應該是一個比較常用的語句,用來統計記錄行數。但是,慢慢地你會發現,這個語句越來越慢了,為什麼呢?MySQL 中,不同的存儲引擎,count(*)的實現方式是不同的。count(*)、count(1)、count(主鍵)、count(欄位)的區別(1)對於count(主鍵id),InnoDB 會遍歷全表,取每行的主鍵 id,返回給 server 層,server 層拿到數據後,進行判斷累加。
  • MySQL 優化案例 - select count-愛可生
    四、原理為了找到答案,通過 Google 查找 MySQL 下 select count(*)的原理,找到了答案。這邊省略過程,直接上結果。mysql> create index idx_rowguid on api_runtime_log(rowguid);Query OK, 0 rows affected (0.01 sec)Records: 0 Duplicates: 0 Warnings: 0mysql> select count(*) from api_runtime_log;++| count(
  • MySQL的COUNT語句,竟然都能被面試官虐的這麼慘!?
    即以下表記錄create table insert insert insert insert insert insert insert 使用語句count(*),count(id),count(id2)查詢結果如下:select count(*),count
  • 淺談MySQL 統計行數的 count
    MySQL count() 函數我們並不陌生,用來統計每張表的行數。但如果你的表越來越大,且是 InnoDB 引擎的話,會發現計算的速度會越來越慢。在這篇文章裡,會先介紹 count() 實現的原理及原因,然後是 count 不同用法的性能分析,最後給出需要頻繁改變並需要統計表行數的解決方案。
  • mysql中count()太慢,我該怎麼辦
    ,我和你介紹了在mysql中,count()實現原理,以及count()的各種使用方式,和他們之間的差異,以及執行效率的比較,看完之後,想必你不會在說,count(*)執行效率是最慢的了吧,經過mysql優化後的count(*)的執行效率和count(1)已經不相上下了,但是當表中的數據量很大的時候,無論何種形式的count(),執行的效率都會很低,畢竟需要逐行統計計數。
  • MySQL的COUNT是怎麼執行的
    由於0並不是NULL,server層給count變量加1。•server層向InnoDB要下一條記錄。•InnoDB通過二級索引記錄的next_record屬性找到下一條二級索引記錄,並返回給server層。•server層繼續給count變量加1。•... 重複上述過程,直到InnoDB向server層返回沒記錄可查的消息。
  • Mysql性能優化:為什麼你的count(*)這麼慢?
    今天這篇文章將從Mysql內部對於count函數是怎樣處理的?count的實現方式在Mysql中的不同的存儲引擎對count函數有不同的實現方式。MyISAM引擎把一個表的總行數存在了磁碟上,因此執行count(*)的時候會直接返回這個數,效率很高(沒有where查詢條件)。InnoDB引擎並沒有直接將總數存在磁碟上,在執行count(*)函數的時候需要一行一行的將數據讀出來,然後累計總數。
  • 小王:MySQL count(1) 真的比 count(*) 快麼?
    在 MySQL 5.5 以後默認引擎切換為 InnoDB,InnoDB 因為增加了版本控制(MVCC)的原因,同時有多個事務訪問數據並且有更新操作的時候,每個事務需要維護自己的可見性,那麼每個事務查詢到的行數也是不同的,所以不能緩存具體的行數,他每次都需要 count 一下所有的行數。那麼 count(1) 和 count(*)有區別麼?
  • mysql中這樣的場景就不要用count了
    那你是怎麼開發實現的呢?是用類似下面這樣的語句實現的嗎?如果表的數據量不大,那就無所謂了。但是如果表中的數據量很大的話,那就要小心了。這樣的耗時可是很大的,很容易就會把你的服務拖垮的哦。功能明明只是需要查有沒有符合條件的數據就可以,實現時卻把符合條件的所有數據的數量都統計出來了。是不是有點做得過多了呢。那到底是要怎麼做呢。隨我看看下面的這個sql吧。這個sql只要查詢到一條符合條件的數據就會返回了。
  • count mysql 當天數據 - CSDN
    (create_time) = TO_DAYS(NOW())注意:這裡的create_time是資料庫中的時間欄位二、查詢昨天(固定幾天前)的記錄:select count(1) from kunyao_shop_order where is_payed = 1
  • MySQL最常用分組聚合函數
    mysql> select count(*) from salary_tab where salary='1000';++| count(*) |++| 2 |++mysql> select count(*) from salary_tab;  #沒有條件,默認統計表數據行數++| count
  • MySQL中只會count(),sum()?累加運算沒聽過?
    這個統計sql還是花了我一小段時間的。mysql統計這個還是需要一定的技巧的。sql如下: select reg_time, count(user_id) daily_quantityfrom user_infogroup by reg_time通過上面的sql我們很容易得出以下列表:reg_timedaily_quantity2019-09-03
  • MySQL資料庫測試題
    (難度A)A、在DOS提示符下輸入以下命令mysql -uroot -p1234 test<d:/db.sql B、在DOS提示符下輸入以下命令mysqldump -uroot -p1234 test<d:/db.sql