今天做項目的時候看mybatis-plus打印的sql語句日志中用的是count(1),感覺不是很懂,就查了查
以下內容轉載自:https://blog.csdn.net/qq_34827674/article/details/122336347
首先說一下count()這個函數,count()函數的作用是統計符合查詢條件的記錄中,函數指定的參數不為 NULL 的記錄有多少個。比如count(age),如果有某一條記錄的age字段為null,則不會被統計進去。
count(1)是統計在某個表中,1這個表達式不為null的記錄有多少,而1永遠不為null,所以就是統計一個表中的記錄有多少條。
在通過 count 函數統計有多少個記錄時,MySQL 的 server 層會維護一個名叫 count 的變量。
server 層會循環向 InnoDB 讀取一條記錄,如果 count 函數指定的參數不為 NULL,那么就會將變量 count 加 1,直到符合查詢的全部記錄被讀完,就退出循環。最后將 count 變量的值發送給客戶端。
InnoDB 是通過 B+ 樹來保持記錄的,根據索引的類型又分為聚簇索引和二級索引,它們區別在於,聚簇索引的葉子節點存放的是實際數據,而二級索引的葉子節點存放的是主鍵值,而不是實際數據。
例如:select count(id) from t_order;
如果表里只有主鍵索引,沒有二級索引時,那么,InnoDB 循環遍歷聚簇索引,將讀取到的記錄返回給 server 層,然后讀取記錄中的 id 值,就會 id 值判斷是否為 NULL,如果不為 NULL,就將 count 變量加 1。
但是,如果表里有二級索引的時候,InnoDB 循環遍歷的對象就不是聚簇索引,而是二級索引。
這是因為相同數量的二級索引記錄可以比聚簇索引記錄占用更少的存儲空間,所以二級索引樹比聚簇索引樹小,這樣遍歷二級索引的 I/O 成本比遍歷聚簇索引的 I/O 成本小,因此「優化器」優先選擇的是二級索引。
那么count(1) 執行過程是怎樣的?
如果表里只有主鍵索引,沒有二級索引時。
那么,InnoDB 循環遍歷聚簇索引(主鍵索引),將讀取到的記錄返回給 server 層,但是不會讀取記錄中的任何字段的值,因為 count 函數的參數是 1,不是字段,所以不需要讀取記錄中的字段值。參數 1 很明顯並不是 NULL,因此 server 層每從 InnoDB 讀取到一條記錄,就將 count 變量加 1。
可以看到,count(1) 相比 count(主鍵字段) 少一個步驟,就是不需要讀取記錄中的字段值,所以通常會說 count(1) 執行效率會比 count(主鍵字段) 高一點。
但是,如果表里有二級索引時,InnoDB 循環遍歷的對象就二級索引了。
那count(*) 呢?
count(*)並不是讀取記錄中的所有字段值,count(*) 其實等於 count(0),也就是說,當你使用 count(*) 時,MySQL 會將 * 參數轉化為參數 0 來處理。所以,count(*) 執行過程跟 count(1) 執行過程基本一樣的,性能沒有什么差異。
而且 MySQL 會對 count(*) 和 count(1) 有個優化,如果有多個二級索引的時候,優化器會使用key_len 最小的二級索引進行掃描。
只有當沒有二級索引的時候,才會采用主鍵索引來進行統計。
那么count(字段) 執行過程是怎樣的?
count(字段) 的執行效率相比前面的 count(1)、 count(*)、 count(主鍵字段) 執行效率是最差的。
對於這個count(普通字段)來說,會采用全表掃描的方式來計數,所以它的執行效率是比較差的。
所以count(*)=count(1)>count(主鍵)>count(字段)
為什么要通過遍歷的方式來計數?
前面的案例都是基於 Innodb 存儲引擎來說明的,但是在 MyISAM 存儲引擎里,執行 count 函數的方式是不一樣的,通常在沒有任何查詢條件下的 count(*),MyISAM 的查詢速度要明顯快於 InnoDB。
使用 MyISAM 引擎時,執行 count 函數只需要 O(1 )復雜度,這是因為每張 MyISAM 的數據表都有一個 meta 信息有存儲了row_count值,由表級鎖保證一致性,所以直接讀取 row_count 值就是 count 函數的執行結果。
而 InnoDB 存儲引擎是支持事務的,同一個時刻的多個查詢,由於多版本並發控制(MVCC)的原因,InnoDB 表“應該返回多少行”也是不確定的,所以無法像 MyISAM一樣,只維護一個 row_count 變量。
舉個例子,假設表 t_order 有 100 條記錄,現在有兩個會話並行以下語句:
會話A | 會話B |
begin; | |
select * from t_order (返回100) |
|
insert into t_order(插入一條記錄) | |
select * from t_order (返回100) |
select * from t_user (返回101) |
在會話 A 和會話 B的最后一個時刻,同時查表 t_order 的記錄總個數,可以發現,顯示的結果是不一樣的。所以,在使用 InnoDB 存儲引擎時,就需要掃描表來統計具體的記錄。
而當帶上 where 條件語句之后,MyISAM 跟 InnoDB 就沒有區別了,它們都需要掃描表來進行記錄個數的統計。