MySQL知識點補充(不同count()的用法、union執行流程、group by語句)

一、MySQL中不同count()的用法

count()是一個聚合函數,對於返回的結果集,一行行地判斷,如果count函數的參數不是NULL,累計值就加1,否則不加。最後返回累計值

1.對於count(主鍵id)來說,InnoDB引擎會遍歷整張表,把每一行的id值都取出來,返回給server層。server層拿到id後,判斷是不可能爲空的,就按行累加

2.對於count(1)來說,InnoDB引擎遍歷整張表,但不取值。server層對於返回的每一行,放一個數字1進入,判斷是不可能爲空的,按行累加

3.對於count(字段)來說,如果這個字段是定義爲not null的話,一行行地從記錄裏面讀出這個字段,判斷不能爲null,按行累加;如果這個字段定義允許爲null的話,那麼執行的時候,判斷到有可能是null,還要把值取出來在判斷一下,不是null才累加

4.對於count(*)來說,並不會把全部字段取出來,而是專門做了優化。不取值,count(*)肯定不是null,按行累加

按照效率排序count()<count(id)<count(1)count()count(字段)<count(主鍵id)<count(1)≈count(*),所以儘量使用count(*)

二、union執行流程

爲了便於量化分析,以下面表t1來舉例

create table t1(id int primary key, a int, b int, index(a));

CREATE DEFINER=`root`@`%` PROCEDURE `idata`()
BEGIN
	declare i int;
  set i=1;
  while(i<=1000)do
    insert into t1 values(i, i, i);
    set i=i+1;
  end while;

END     

分析下面這條SQL語句:

(select 1000 as f) union (select id from t1 order by id desc limit 2);

union的語義是取這兩個子查詢結果的並集。並集的意思是這兩個集合加起來,重複的行只保留一行
在這裏插入圖片描述

  • 第二行的key=PRIMARY,說明第二個子句用到了索引id
  • 第三行的Extra字段,表示在對子查詢的結果集做union的時候,使用了臨時表

這個語句的執行流程如下:

1.創建一個內存臨時表,這個臨時表只有一個整型字段f,並且f是主鍵字段

2.執行第一個子查詢,得到1000這個值

3.執行第二個子查詢:

  • 拿到第一行id=1000,試圖插入臨時表中。但由於1000這個值已經存在於臨時表了,違反了唯一性約束,所以插入失敗,然後繼續執行
  • 取到第二行id=999,插入臨時表成功

4.從臨時表中按行取出數據,返回結果,並刪除臨時表,結果中包含兩行數據分別是1000和999
在這裏插入圖片描述
這裏的內存臨時表起到了暫存數據的作用,而且計算過程還用上了臨時表主鍵id的唯一性約束,實現了union的語義

如果把上面的語句中union改成union all的話,就沒有了去重的語義。這樣執行的時候,就依次執行子查詢,得到的結果直接作爲結果集的一部分,發給客戶端。因此也就不需要臨時表了
在這裏插入圖片描述
第二行Extra字段顯示的是Using index,表示只使用了覆蓋索引,沒有用臨時表

三、group by語句詳解

1、group by執行流程

還是使用上面的表t1,分析下面這條SQL語句:

select id%10 as m, count(*) as c from t1 group by m;

這個語句的邏輯是把表t1裏的數據,按照id%10進行分組統計,並按照m的結果排序後輸出。explain結果如下:
在這裏插入圖片描述
在Extra字段裏面,可以看到三個信息:

  • Using index,表示這個語句使用了覆蓋索引,選擇了索引a,不需要回表
  • Using temporary,表示使用了臨時表
  • Using filesort,表示需要排序

這個語句的執行流程如下:

1.創建內存臨時表,表裏有兩個字段m和c,主鍵是m

2.掃描表t1的索引a,依次取出葉子節點上的id值,計算id%10的結果,記爲x

  • 如果臨時表中沒有主鍵爲x的行,就插入一個記錄(x,1)
  • 如果表中有主鍵爲x的行,就將x這一行的c值加1

3.遍歷完成後,再根據字段m做排序,得到結果集返回給客戶端
在這裏插入圖片描述
內存臨時表排序流程圖:
在這裏插入圖片描述在這裏插入圖片描述
如果並不需要對結果進行排序,在SQL語句末尾增加order by null:

select id%10 as m, count(*) as c from t1 group by m order by null;

在這裏插入圖片描述
由於表t1中的id值是從1開始的,因此返回的結果集中第一行是id=1

這個例子裏由於臨時表只有10行,內存可以放得下,因此全程只使用了內存臨時表。但是,內存臨時表的大小是有限的,參數tmp_table_size就是控制整個內存大小的,默認是16M

set tmp_table_size=1024;
select id%100 as m, count(*) as c from t1 group by m order by null limit 10;

把內存臨時表的大小限制爲最大1024字節,並把語句改成id%100,這樣返回結果裏有100行數據。但是,這時的內存臨時表大小不夠存下這100行數據,也就是說,執行過程中會發現內存臨時表大小達到了上限。那麼,這時候會把內存臨時錶轉成磁盤臨時表,磁盤臨時表默認使用的引擎是InnoDB

2、group by優化方法——索引

group by的語義邏輯,是統計不同的值的個數。但是,由於每一行的id%100的結果是無序的,所以就需要有一個臨時表來記錄並統計結果。那麼,如果掃描過程中可以保證出現的數據是有序的就可以了

假設,現在有一個類似下圖的這麼一個數據結構
在這裏插入圖片描述
如果可以確保輸入的數據是有序的,那麼計算group by的時候,就只需要從左到右,順序掃描,依次累加。也就是下面這個流程:

  • 當碰到第一個1的時候,已經知道累積了X個0,結果集裏的第一行就是(0,X)
  • 當碰到第一個2的時候,已經知道累積了Y個1,結果集裏的第一行就是(1,Y)

按照這個邏輯執行的話,掃描到整個輸入的數據結束,就可以拿到group by的結果,不需要臨時表,也需要再額外排序

在MySQL5.7版本支持了generated column機制,用來實現列數據的關聯更新。創建一個列z,在z列上創建一個索引

alter table t1 add column z int generated always as(id % 100), add index(z);

這樣,索引z上的數據就是有序的了。group by語句就可以改成:

select z, count(*) as c from t1 group by z;

在這裏插入圖片描述
從這個Extra字段可以看到,這個語句的執行不再需要臨時表,也不需要排序了

3、group by優化方法——直接排序

在group by語句中加入SQL_BIG_RESULT這個提示,就可以告訴優化器:這個語句涉及的數據量很大,直接用磁盤臨時表。因爲磁盤臨時表是B+樹存儲,存儲效率不如數組來得高。所以MySQL優化器直接用數組來存

select SQL_BIG_RESULT id%100 as m, count(*) as c from t1 group by m;

1.初始化sort_buffer,確定放入一個整型字段,記爲m

2.掃描表t1的索引a,依次取出裏面的id值,將id%100的值存入sort_buffer中

3.掃描完成後,對sort_buffer的字段m做排序(如果sort_buffer內存不夠用,就會利用磁盤臨時文件輔助排序)

4.排序完成後,就得到了一個有序數組

根據有序數組,得到數組裏面的不同值,以及每個值的出現次數
在這裏插入圖片描述在這裏插入圖片描述
這個語句的執行沒有再使用臨時表,而是直接用了排序算法


總結:

MySQL相關內容的梳理告一段落了,這裏強烈推薦極客時間中林曉斌老師的《MySQL實戰45講》專欄,讓我對MySQL整個知識體系有了新的理解。這篇博客算是對之前沒有提到的MySQL相關知識的補充,後面如果學習到新的知識也會繼續進行補充的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章