1. 程式人生 > >MySQL - - 索引原理與慢查詢優化

MySQL - - 索引原理與慢查詢優化

詳解 limit 性能 取出 平衡 獲取數據 目的 官方文檔 百度

目錄

  • 索引介紹
  • 索引方法
  • 索引類型
  • 聚合索引和輔助索引
  • 測試索引
  • 正確使用索引
  • 組合索引
  • 註意事項
  • 查詢計劃
  • 慢日誌查詢
  • 大數據量分頁優化
  • EXPLAIN 詳解

1, 索引介紹

  • 需求:
    • 一般的應用系統,讀寫比例在10:1左右,而且插入操作和一般的更新操作很少出現性能問題,在生產環境中,我們遇到最多的,也是最容易出問題的,還是一些復雜的查詢操作,因此對查詢語句的優化顯然是重中之重。
      說起加速查詢,就不得不提到索引了。
  • 索引:
    • 簡單的說,相當於圖書的目錄,可以幫助用戶快速的找到需要的內容.
    • 在MySQL中也叫做“鍵”,是存儲引擎用於快速找到記錄的一種數據結構。能夠大大提高查詢效率。特別是當數據量非常大,查詢涉及多個表時,使用索引往往能使查詢速度加快成千上萬倍.
  • 本質:
    • 索引本質:通過不斷地縮小想要獲取數據的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,我們可以總是用同一種查找方式來鎖定數據。

2, 索引方法

2.1 B+TREE 索引

  • B+樹是一種經典的數據結構,由平衡樹和二叉查找樹結合產生,它是為磁盤或其它直接存取輔助設備而設計的一種平衡查找樹,在B+樹中,所有的記錄節點都是按鍵值大小順序存放在同一層的葉節點中,葉節點間用指針相連,構成雙向循環鏈表,非葉節點(根節點、枝節點)只存放鍵值,不存放實際數據。下面看一個2層B+樹的例子:

技術分享圖片

  • 註意:通常其高度都在2~3層,查詢時可以有效減少IO次數。

技術分享圖片

  • 系統從磁盤讀取數據到內存時是以磁盤塊(block)為基本單位的,位於同一磁盤塊中的數據會被一次性讀取出來,而不是按需讀取。InnoDB 存儲引擎使用頁作為數據讀取單位,頁是其磁盤管理的最小單位,默認 page 大小是 16kB。
  • b+樹的查找過程
    • 如圖所示,如果要查找數據項30,那麽首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找確定30在28和65之間,鎖定磁盤塊1的P2指針,內存時間因為非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊由磁盤加載到內存,發生第二次IO,30在28和35之間,鎖定當前磁盤塊的P1指針,通過指針加載磁盤塊到內存,發生第三次IO,同時內存中做二分查找找到30,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那麽總共需要百萬次的IO,顯然成本非常非常高。
  • 強烈註意: 索引字段要盡量的小,磁盤塊可以存儲更多的索引.

2.2 HASH 索引

  • hash就是一種(key=>value)形式的鍵值對,允許多個key對應相同的value,但不允許一個key對應多個value,為某一列或幾列建立hash索引,就會利用這一列或幾列的值通過一定的算法計算出一個hash值,對應一行或幾行數據. hash索引可以一次定位,不需要像樹形索引那樣逐層查找,因此具有極高的效率.

技術分享圖片

  • 假設索引使用hash函數f( ),如下:
f(‘Arjen‘) = 2323
f(‘Baron‘) = 7437
f(‘Peter‘) = 8784
f(‘Vadim‘) = 2458
  • 此時,索引的結構大概如下:

技術分享圖片

2.3 HASH與BTREE比較:

hash類型的索引:查詢單條快,範圍查詢慢
btree類型的索引:b+樹,層數越多,數據量越大,範圍查詢和隨機查詢快(innodb默認索引類型)

不同的存儲引擎支持的索引類型也不一樣
InnoDB 支持事務,支持行級別鎖定,支持 Btree、Hash 等索引,不支持Full-text 索引;
MyISAM 不支持事務,支持表級別鎖定,支持 Btree、Full-text 等索引,不支持 Hash 索引;
Memory 不支持事務,支持表級別鎖定,支持 Btree、Hash 等索引,不支持 Full-text 索引;
NDB 支持事務,支持行級別鎖定,支持 Hash 索引,不支持 Btree、Full-text 等索引;
Archive 不支持事務,支持表級別鎖定,不支持 Btree、Hash、Full-text 等索引;

3, 索引類型

  • MySQL中常見索引有:
    • 普通索引
    • 唯一索引
    • 主鍵索引
    • 組合索引

3.1 普通索引

  • 普通索引僅有一個功能:加速查詢
  • 創建表+索引
#創建表同時添加name字段為普通索引
create table tb1(
   id int not null auto_increment primary key,
   name varchar(100) not null,
   index idx_name(name)  
);
  • 創建索引
#單獨為表指定普通索引

create index idx_name on tb1(name);
  • 刪除索引
drop index idx_name on tb1;
  • 查看索引
show index from tb1;
  • 查看索引 列介紹
1、Table 表的名稱。

2、 Non_unique 如果索引為唯一索引,則為0,如果可以則為1。

3、 Key_name 索引的名稱

4、 Seq_in_index 索引中的列序列號,從1開始。

5、 Column_name 列名稱。

6、 Collation 列以什麽方式存儲在索引中。在MySQL中,有值‘A’(升序)或NULL(無分類)。

7、Cardinality 索引中唯一值的數目的估計值。

8、Sub_part 如果列只是被部分地編入索引,則為被編入索引的字符的數目。如果整列被編入索引,則為NULL。

9、 Packed 指示關鍵字如何被壓縮。如果沒有被壓縮,則為NULL。

10、 Null 如果列含有NULL,則含有YES。如果沒有,則該列含有NO。

11、 Index_type 用過的索引方法(BTREE, FULLTEXT, HASH, RTREE)。

12、 Comment 多種評註

3.2 唯一索引

  • 唯一索引有兩個功能:加速查詢 和 唯一約束(可含一個null 值)
  • 創建表+唯一(unique)索引
create table tb2(
  id int not null auto_increment primary key,
  name varchar(50) not null,
  age int not null,
  unique index idx_age (age)   
)
  • 創建unique索引
create unique index idx_age on tb2(age);

3.3 主鍵索引

  • 主鍵有兩個功能:加速查詢 和 唯一約束(不可含null)
  • 註意:一個表中最多只能有一個主鍵索引
  • 創建表 + 創建主鍵
#方式一:
create table tb3(
   id int not null auto_increment primary key,
   name varchar(50) not null,
   age int default 0 
);

#方式二:
create table tb3(
   id int not null auto_increment,
   name varchar(50) not null,
   age int default 0 ,
   primary key(id)
);
  • 創建主鍵
alter table tb3 add primary key(id);
  • 刪除主鍵
#方式一
alter table tb3 drop primary key;

#方式二:
#如果當前主鍵為自增主鍵,則不能直接刪除.需要先修改自增屬性,再刪除

alter table tb3 modify id int ,drop primary key;

3.4 組合索引

  • 組合索引是將n個列組合成一個索引
  • 其應用場景為:頻繁的同時使用n列來進行查詢,如:where n1 = ‘alex‘ and n2 = 666。
  • 創建表+組合索引
create table tb4(
  id int not null ,
  name varchar(50) not null,
  age int not null,
  index idx_name_age (name,age)   
)
  • 創建組合索引
create index idx_name_age on tb4(name,age);
  • 索引應用場景
舉個例子來說,比如你在為某商場做一個會員卡的系統。

這個系統有一個會員表
有下列字段:
會員編號 INT
會員姓名 VARCHAR(10)
會員身份證號碼 VARCHAR(18)
會員電話 VARCHAR(10)
會員住址 VARCHAR(50)
會員備註信息 TEXT

那麽這個 會員編號,作為主鍵,使用 PRIMARY
會員姓名 如果要建索引的話,那麽就是普通的 INDEX
會員身份證號碼 如果要建索引的話,那麽可以選擇 UNIQUE (唯一的,不允許重復)

4, 聚合索引和輔助索引

  • 數據庫中的B+樹索引可以分為聚集索引和輔助索引.
  • 聚集索引:InnoDB表 索引組織表,即表中數據按主鍵B+樹存放,葉子節點直接存放整條數據,每張表只能有一個聚集索引。
  • 如圖:

技術分享圖片

  • 1 當你定義一個主鍵時,InnnodDB存儲引擎則把它當做聚集索引
  • 2 如果你沒有定義一個主鍵,則InnoDB定位到第一個唯一索引,且該索引的所有列值均飛空的,則將其當做聚集索引。
  • 3 如果表沒有主鍵或合適的唯一索引INNODB會產生一個隱藏的行ID值6字節的行ID聚集索引,
  • 補充:由於實際的數據頁只能按照一顆B+樹進行排序,因此每張表只能有一個聚集索引,聚集索引對於主鍵的排序和範圍查找非常有利.
  • 輔助索引:(也稱非聚集索引)是指葉節點不包含行的全部數據,葉節點除了包含鍵值之外,還包含一個書簽連接,通過該書簽再去找相應的行數據。下圖顯示:

技術分享圖片

  • InnoDB存儲引擎輔助索引獲得數據的查找方式:

技術分享圖片

  • 從上圖中可以看出,輔助索引葉節點存放的是主鍵值,獲得主鍵值後,再從聚集索引中查找整行數據。舉個例子,如果在一顆高度為3的輔助索引中查找數據,首先從輔助索引中獲得主鍵值(3次IO),接著從高度為3的聚集索引中查找以獲得整行數據(3次IO),總共需6次IO。一個表上可以存在多個輔助索引。
  • 總結二者區別:
    • 相同的是:不管是聚集索引還是輔助索引,其內部都是B+樹的形式,即高度是平衡的,葉子結點存放著所有的數據。
    • 不同的是:聚集索引葉子結點存放的是一整行的信息,而輔助索引葉子結點存放的是單個索引列信息.
  • 何時使用聚集索引或非聚集索引

  • 下面的表總結了何時使用聚集索引或非聚集索引

動作描述 使用聚集索引 使用非聚集索引
列經常被分組排序
返回某範圍內的數據 不應
一個或極少不同值 不應 不應
頻繁更新的列 不應
外鍵列
主鍵列
頻繁修改索引列 不應

5, 測試索引

5.1 創建數據

-- 1.創建表
CREATE TABLE userInfo(
    id int NOT NULL,
    name VARCHAR(16) DEFAULT NULL,
    age int,
    sex char(1) not null,
    email varchar(64) default null
)ENGINE=MYISAM DEFAULT CHARSET=utf8;
  • 註意:MYISAM存儲引擎 不產生引擎事務,數據插入速度極快,為方便快速插入測試數據,等我們插完數據,再把存儲類型修改為InnoDB  

5.2 創建存儲過程,插入數據

-- 2.創建存儲過程
delimiter$$
CREATE PROCEDURE insert_user_info(IN num INT)
BEGIN
    DECLARE val INT DEFAULT 0;
    DECLARE n INT DEFAULT 1;
    -- 循環進行數據插入
    WHILE n <= num DO
        set val = rand()*50;
        INSERT INTO userInfo(id,name,age,sex,email)values(n,concat(‘alex‘,val),rand()*50,if(val%2=0,‘女‘,‘男‘),concat(‘alex‘,n,‘@qq.com‘));
        set n=n+1;
    end while;
END $$
delimiter;

5.3 調用存儲過程,插入500萬條數據

call insert_user_info(5000000);

技術分享圖片

5.4 此步驟可以忽略。修改引擎為INNODB

ALTER TABLE userinfo ENGINE=INNODB;

技術分享圖片

5.5 測試索引

  • 1 在沒有索引的前提下測試查詢速度
SELECT * FROM userinfo WHERE id = 4567890;

技術分享圖片

  • 註意:無索引情況,mysql根本就不知道id等於4567890的記錄在哪裏,只能把數據表從頭到尾掃描一遍,此時有多少個磁盤塊就需要進行多少IO操作,所以查詢速度很慢.

  • 2 在表中已經存在大量數據的前提下,為某個字段段建立索引,建立速度會很慢

CREATE INDEX idx_id on userinfo(id);

技術分享圖片

  • 3 在索引建立完畢後,以該字段為查詢條件時,查詢速度提升明顯
select * from userinfo where id  = 4567890;

技術分享圖片

  • 註意:
    • 1 mysql先去索引表裏根據b+樹的搜索原理很快搜索到id為4567890的數據,IO大大降低,因而速度明顯提升
    • 2 我們可以去mysql的data目錄下找到該表,可以看到添加索引後該表占用的硬盤空間多了 
    • 3 如果使用沒有添加索引的字段進行條件查詢,速度依舊會很慢(如圖:)

技術分享圖片

6, 正確使用索引

  • 數據庫表中添加索引後確實會讓查詢速度起飛,但前提必須是正確的使用索引來查詢,如果以錯誤的方式使用,則即使建立索引也會不奏效。
  • 即使建立索引,索引也不會生效,例如:
#1. 範圍查詢(>、>=、<、<=、!= 、between...and)
    #1. = 等號
    select count(*) from userinfo where id = 1000 -- 執行索引,索引效率高
    
    #2. > >= < <= between...and 區間查詢
    select count(*) from userinfo where id <100; -- 執行索引,區間範圍越小,索引效率越高
    
    select count(*) from userinfo where id >100; -- 執行索引,區間範圍越大,索引效率越低
    
    select count(*) from userinfo where id between 10 and 500000; -- 執行索引,區間範圍越大,索引效率越低
    
   #3. != 不等於
   select count(*) from userinfo where id != 1000;  -- 索引範圍大,索引效率低
   
   
#2.like ‘%xx%‘
    #為 name 字段添加索引
    create index idx_name on userinfo(name);
    
    select count(*) from userinfo where name like ‘%xxxx%‘; -- 全模糊查詢,索引效率低
    select count(*) from userinfo where name like ‘%xxxx‘;   -- 以什麽結尾模糊查詢,索引效率低
  
    #例外: 當like使用以什麽開頭會索引使用率高
    select * from userinfo where name like ‘xxxx%‘; 

#3. or 
    select count(*) from userinfo where id = 12334 or email =‘xxxx‘; -- email不是索引字段,索引此查詢全表掃描
    
    #例外:當or條件中有未建立索引的列才失效,以下會走索引
    select count(*) from userinfo where id = 12334 or name = ‘alex3‘; -- id 和 name 都為索引字段時, or條件也會執行索引

#4.使用函數
    select count(*) from userinfo where reverse(name) = ‘5xela‘; -- name索引字段,使用函數時,索引失效
    
    #例外:索引字段對應的值可以使用函數,我們可以改為一下形式
    select count(*) from userinfo where name = reverse(‘5xela‘);

#5.類型不一致
    #如果列是字符串類型,傳入條件是必須用引號引起來,不然...
    select count(*) from userinfo where name = 454;
        
    #類型一致
    select count(*) from userinfo where name = ‘454‘;

#6.order by
    #排序條件為索引,則select字段必須也是索引字段,否則無法命中  
    select email from userinfo ORDER BY name DESC; -- 無法命中索引

    select name from userinfo ORDER BY name DESC;  -- 命中索引
        
    #特別的:如果對主鍵排序,則還是速度很快:
    select id from userinfo order by id desc;

7, 組合索引

  • 組合索引: 是指對表上的多個列組合起來做一個索引.
  • 組合索引好處:簡單的說有兩個主要原因:
    • "一個頂三個"。建了一個(a,b,c)的組合索引,那麽實際等於建了(a),(a,b),(a,b,c)三個索引,因為每多一個索引,都會增加寫操作的開銷和磁盤空間的開銷。對於大量數據的表,這可是不小的開銷!
    • 索引列越多,通過索引篩選出的數據越少。有1000W條數據的表,有如下sql:select * from table where a = 1 and b =2 and c = 3,假設假設每個條件可以篩選出10%的數據,如果只有單值索引,那麽通過該索引能篩選出1000W10%=100w 條數據,然後再回表從100w條數據中找到符合b=2 and c= 3的數據,然後再排序,再分頁;如果是組合索引,通過索引篩選出1000w 10% 10% 10%=1w,然後再排序、分頁,哪個更高效,一眼便知
  • 最左匹配原則: 從左往右依次使用生效,如果中間某個索引沒有使用,那麽斷點前面的索引部分起作用,斷點後面的索引沒有起作用;
select * from mytable where a=3 and b=5 and c=4;
   #abc三個索引都在where條件裏面用到了,而且都發揮了作用

select * from mytable where  c=4 and b=6 and a=3;
  #這條語句列出來只想說明 mysql沒有那麽笨,where裏面的條件順序在查詢之前會被mysql自動優化,效果跟上一句一樣

select * from mytable where a=3 and c=7;
  #a用到索引,b沒有用,所以c是沒有用到索引效果的

select * from mytable where a=3 and b>7 and c=3;
  #a用到了,b也用到了,c沒有用到,這個地方b是範圍值,也算斷點,只不過自身用到了索引

select * from mytable where b=3 and c=4;
  #因為a索引沒有使用,所以這裏 bc都沒有用上索引效果

select * from mytable where a>4 and b=7 and c=9;
  #a用到了  b沒有使用,c沒有使用

select * from mytable where a=3 order by b;
  #a用到了索引,b在結果排序中也用到了索引的效果

select * from mytable where a=3 order by c;
  #a用到了索引,但是這個地方c沒有發揮排序效果,因為中間斷點了

select * from mytable where b=3 order by a;
  #b沒有用到索引,排序中a也沒有發揮索引效果

8, 註意事項

  • 1 避免使用select *
  • 2 其他數據庫中使用count(1)或count(列) 代替 count(),而mysql數據庫中count()經過優化後,效率與前兩種基本一樣.
  • 3 創建表時盡量時 char 代替 varchar
  • 4 表的字段順序固定長度的字段優先
  • 5 組合索引代替多個單列索引(經常使用多個條件查詢時)
  • 6 使用連接(JOIN)來代替子查詢(Sub-Queries)
  • 7 不要有超過4個以上的表連接(JOIN)
  • 8 優先執行那些能夠大量減少結果的連接。
  • 9 連表時註意條件類型需一致
  • 10 索引散列值不適合建索引,例:性別不適合

9, 查詢計劃

  • explain + 查詢SQL - 用於顯示SQL執行信息參數,根據參考信息可以進行SQL優化
explain  select count(*) from userinfo where  id = 1;

技術分享圖片

執行計劃:讓mysql預估執行操作(一般正確)
  type : 查詢計劃的連接類型, 有多個參數,先從最佳類型到最差類型介紹

  性能: null > system/const > eq_ref > ref > ref_or_null > index_merge >  range > index >  all 

    慢:
        explain select * from userinfo where email=‘alex‘;
        type: ALL(全表掃描)
        特別的: select * from userinfo limit 1;
    快:
        explain select * from userinfo where name=‘alex‘;
        type: ref(走索引)

10, 慢日誌查詢

  • 慢查詢日誌
    • 將mysql服務器中影響數據庫性能的相關SQL語句記錄到日誌文件,通過對這些特殊的SQL語句分析,改進以達到提高數據庫性能的目的。
  • 慢查詢日誌參數:
long_query_time     :  設定慢查詢的閥值,超出設定值的SQL即被記錄到慢查詢日誌,缺省值為10s
slow_query_log      :  指定是否開啟慢查詢日誌
log_slow_queries    :  指定是否開啟慢查詢日誌(該參數已經被slow_query_log取代,做兼容性保留)
slow_query_log_file :  指定慢日誌文件存放位置,可以為空,系統會給一個缺省的文件host_name-slow.log
log_queries_not_using_indexes: 如果值設置為ON,則會記錄所有沒有利用索引的查詢.
  • 查看 MySQL慢日誌信息
#.查詢慢日誌配置信息 :
show variables like ‘%query%‘;
#.修改配置信息
set global slow_query_log  = on;
  • 查看不使用索引參數狀態:
# 顯示參數  
show variables like ‘%log_queries_not_using_indexes‘;
# 開啟狀態
set global log_queries_not_using_indexes  = on;
  • 查看慢日誌顯示的方式
#查看慢日誌記錄的方式
show variables like ‘%log_output%‘;
 
#設置慢日誌在文件和表中同時記錄
set global log_output=‘FILE,TABLE‘;
  • 測試慢查詢日誌
#查詢時間超過10秒就會記錄到慢查詢日誌中
select sleep(3) FROM user ;
 
#查看表中的日誌
select * from mysql.slow_log;

11, 大數據量分頁優化

  • 執行此段代碼:
select * from userinfo limit 3000000,10;
  • 優化方案:
  • 1 簡單粗暴,就是不允許查看這麽靠後的數據,比如百度就是這樣的

技術分享圖片

  • 最多翻到72頁就不讓你翻了,這種方式就是從業務上解決;

  • 2 在查詢下一頁時把上一頁的行id作為參數傳遞給客戶端程序,然後sql就改成了

select * from userinfo where id>3000000 limit 10;
  • 這條語句執行也是在毫秒級完成的,id>300w其實就是讓mysql直接跳到這裏了,不用依次在掃描全面所有的行
  • 如果你的table的主鍵id是自增的,並且中間沒有刪除和斷點,那麽還有一種方式,比如100頁的10條數據
select * from userinfo where id>100*10 limit 10;
  • 3 最後第三種方法:延遲關聯

  • 分析一下這條語句為什麽慢,慢在哪裏。

select * from userinfo limit 3000000,10;
  • 玄機就處在這個 * 裏面,這個表除了id主鍵肯定還有其他字段 比如 name age 之類的,因為select * 所以mysql在沿著id主鍵走的時候要回行拿數據,走一下拿一下數據;
  • 如果把語句改成
select id from userinfo limit 3000000,10;
  • 你會發現時間縮短了一半;然後我們在拿id分別去取10條數據就行了;
  • 語句就改成這樣了:
select table.* from userinfo inner join ( select id from userinfo limit 3000000,10 ) as tmp on tmp.id=userinfo.id;
  • 這三種方法最先考慮第一種 其次第二種,第三種是別無選擇

12, EXPLAIN 詳解

12.1 介紹

  • EXPLAIN 命令用於SQL語句的查詢執行計劃。這條命令的輸出結果能夠讓我們了解MySQL 優化器是如何執行SQL 語句的。這條命令並沒有提供任何調整建議,但它能夠提供重要的信息幫助你做出調優決策。
  • 先解析一條sql語句,你可以看出現什麽內容
EXPLAIN SELECT  * FROM person,dept WHERE person.dept_id = dept.did and person.salary >20000

技術分享圖片

  • 下面咱們詳細的介紹一下 查詢計劃的結果列:

12.2 id : 查詢序列號

  • 查詢序號即為sql語句執行順序
EXPLAIN select * from person where dept_id =(select did from dept where dname =‘python‘);

技術分享圖片

  • 從 2 個表中查詢,對應輸出 2 行,每行對應一個表, id 列表示執行順序,id 越大,越先執行,id 相同時,由上至下執行。

12.3 select_type : 查詢類型

  • select_type 列提供了 對表的查詢類型。最常見的值包括SIMPLE、PRIMARY、DERIVED 和UNION。其他可能的值還有 UNION RESULT、SUBQUERY 等等.

12.3.1 simple 簡單查詢 (沒有union和子查詢)

  • 對於不包含子查詢和其他復雜語法的簡單查詢,這是一個常見的類型。
EXPLAIN SELECT * FROM person;

技術分享圖片

12.3.2 primary 最外層查詢 (在存在子查詢的語句中,最外面的select查詢就是primary)

  • 這是為更復雜的查詢而創建的首要表(也就是最外層的表)。這個類型通常可以在DERIVED 和 UNION 類型混合使用時見到。

12.3.3 derived 子查詢(在FROM列表中包含的子查詢)

  • 當一個表不是一個物理表時,那麽這個就被叫做DERIVED
EXPLAIN SELECT *FROM (SELECT* FROM person LIMIT 5) AS s

技術分享圖片

12.3.4 subquery 映射為子查詢(在SELECT或WHERE列表中包含了子查詢)

  • 這個select-type 的值是為使用子查詢而定義的.
EXPLAIN SELECT person.*,(select 2 from person as p2) FROM person where dept_id = (select did from dept where dname=‘python‘);

技術分享圖片

12.3.5 union 聯合

EXPLAIN SELECT * FROM person union all select * from person ;

技術分享圖片

12.3.6 union result 使用聯合的結果

EXPLAIN SELECT * FROM person union  select * from person ;

技術分享圖片

12.4 table 輸出的行所用的表

EXPLAIN SELECT * FROM person;

技術分享圖片

  • 註意: table 列是EXPLAIN 命令輸出結果中的一個單獨行的唯一標識符。這個值可能是表名、表的別名或者一個為查詢產生臨時表的標識符,如派生表、子查詢或集合。

12.5 type 連接類型

  • type 列代表表示 查詢計劃的連接類型, 有多個參數,先從最佳類型到最差類型介紹 重要且困難
  • 性能: null > system/const > eq_ref > ref > ref_or_null >index_merge > range > index > all

12.5.1 type=NULL 在優化過程中就已得到結果,不用再訪問表或索引。

EXPLAIN SELECT max(id) FROM person;

技術分享圖片

12.5.2 type=const/system 常量

  • 在整個查詢過程中這個表最多只會有一條匹配的行,比如主鍵 id=1 就肯定只有一行;
  • 表最多有一個匹配行,const用於比較primary key 或者unique索引。因為只匹配一行數據,所以一定是用到primary key 或者unique 情況下才會是const,看下面這條語句
EXPLAIN SELECT * FROM person where id =2;

技術分享圖片

  • 所以說可以理解為const是最優化的。

12.5.3 type=eq_ref 使用有唯一性 索引查找(主鍵或唯一性索引)

  • 對於eq_ref的解釋,mysql手冊是這樣說的:"對於每個來自於前面的表的行組合,從該表中讀取一行。這可能是最好的聯接類型,除了const類型。它用在一個索引的所有部分被聯接使用並且索引是UNIQUE或PRIMARY KEY"。eq_ref可以用於使用=比較帶索引的列。看下面的語句
EXPAIN select * from person,dept where person.id = dept.did;
  • 得到的結果是下圖所示。很明顯,mysql使用eq_ref聯接來處理 dept 表.

技術分享圖片

12.5.4 type=ref 非唯一性索引訪問

  • 這是一種索引訪問(有時也叫做索引查找),它返回所有匹配某個單個值的行,然而,它可能會找到多個符合條件的行。因此,它是查找和掃描的混合體,此類索引訪問只有當使用非唯一性索引或者唯一性索引的非唯一性前綴時才會發生。把它叫做ref是因為索引要跟某個參考值相比較。這個參考值或者是一個常數,或者是來自多表查詢前一個表裏的結果值。
EXPLAIN select * from person where name=‘alex‘;

技術分享圖片

12.5.5 ref_or_null 該聯接類型如同ref類似,結果包含空行.

  • 上面這五種情況都是很理想的索引使用情況

12.5.6 type=range

  • 索引範圍掃描,常見於 <,<=,>,>=,between,in等操作符。
EXPLAIN select * from person where id BETWEEN 1 and 5;

技術分享圖片

12.5.7 type=index

  • 該聯接類型與ALL相同都是掃描表,但index只對索引樹進行掃描,而ALL是是對數據表文件的掃描。這通常比ALL快,因為索引文件通常比數據文件小。(也就是說雖然all和Index都是讀全表,但index是從索引中讀取的,而all是從硬盤中讀的)主要優點是避免了排序,因為索引是排好序的。
  • Extra列中看到“Using index”,說明mysql正在使用覆蓋索引,只掃描索引的數據。 
EXPLAIN select id,name from person;

技術分享圖片

12.5.8 type=ALL

  • 對於每個來自於先前的表的行組合,進行完整的表掃描。如果表是第一個沒標記const的表,這通常不好,並且通常在它情況下很差。通常可以增加更多的索引而不要使用ALL,使得行能基於前面的表中的常數值或列值被檢索出。
EXPLAIN select * from person;

技術分享圖片

12.6 possible_keys

  • 該 possible_keys列表示MySQL可以從中選擇查找表中的行的索引。如果此列是NULL,則沒有相關的索引。在這種情況下,您可以通過檢查WHERE 子句來檢查是否引用某些適合索引的列,從而提高查詢的性能。如果是這樣,請創建一個適當的索引並使用 EXPLAIN再次檢查查詢 。
  • 另外如果這個列出現大量可能被使用的索引(例如多於3 個), 那麽這 意味著備選索引數量太多了,同時也可能提示存在無效的索引。

12.7 key

  • 該key 列指出mysql優化器決定選擇使用哪個索引來優化對該表的訪問。一般來說SQL查詢中的每個表都只會使用一個索引。但是也存在索引合並的少數例外情況,如給定表上用到了兩個或者更多索引。查詢過程中由優化器來決定實際使用的索引。如果possible_keys索引列表中沒有適合查找行的索引,那麽這個key可能會命名一個不存在於該possible_keys值中的索引 。簡單且重要

12.8 key_len

  • 該key_len 列定義了mysql在索引裏使用的字節數。如果mysql正在使用的只是索引裏的某些列,那麽就可以用這個值來算出具體是哪些列。在mysql5.5及以前的版本裏,只能使用索引的最左前綴。例如,sakila.film_actor的主鍵是兩個SMALLINT列,並且每個SMALLINT列是兩個字節,那麽索引中的每項是4個字節。也即說明key_len通過查找表的定義而被計算出,而不是表中的數據。
  • 在不損失精確性的情況下,長度越短越好.

12.9 ref

  • ref 列顯示使用哪個列或常數與key一起從表中選擇數據行。指出對 key 列所選擇的索引的查找方式,常見的值有 const, func, NULL, 具體字段名。當 key 列為 NULL ,即不使用索引時 。如果值是func,則使用的值是某個函數的結果
create table a11(id int primary key, age int);
insert into a11 value(1, 10),(2, 10);

mysql> desc select * from a11 where age=10;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | a11   | ALL  | NULL          | NULL | NULL    | NULL |    2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
註意:當 key 列為 NULL , ref 列也相應為 NULL 。

mysql> desc select * from a11 where id=1;
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | a11   | const | PRIMARY       | PRIMARY | 4       | const |    1 |       |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+

註意:這次 key 列使用了主鍵索引,where id=1 中 1 為常量, ref 列的 const 便是指這種常量。

12.10 row

  • 這一列是mysql評估 為了找到所需的行而要讀取的行數。這個數字是內嵌循環關聯計劃裏的循環數目,也就是說它不是mysql認為它最終要從表裏讀取出來的行數,而是mysql為了找到符合查詢的每一點上標準的那些行而必須讀取的行的平均數。
  • rows 列提供了試圖分析所有存在於累計結果集中的行數目的MySQL 優化器估計值。執行計劃很容易描述這個很困難的統計量。
  • 查詢中總的讀操作數量是基於合並之前行的每一行的rows 值的連續積累而得出的。這是一種嵌套行算法。
  • 簡單且重要,數值越大越不好,說明沒有用好索引

12.11 Extra

  • 該列包含 MySQL 查詢的詳細信息。
  • 1 Not exists : 不存在信息
  • 2 range checked for each record :沒有找到合適的索引
  • 3 Using index condition :出現這個說明mysql使用了覆蓋索引,避免訪問了表的數據行,效率不錯!
建表及插入數據:
create table a13 (id int primary key, age int);
insert into a13 value(1, 10),(2, 10);
mysql> explain select id from a13;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1  | SIMPLE        | a13   | NULL         | index | NULL            | PRIMARY | 4   | NULL|  2   | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
因為 id 為主鍵索引,索引中直接包含了 id 的值,所以無需訪問表,直接查找索引就能返回結果。

mysql> explain select age from a13;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1  | SIMPLE        | a13   | NULL         | ALL  | NULL            | NULL |  NULL  | NULL|  2   |  NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
age 列沒有索引,因此沒有 Using index ,意即需要訪問表。
為 age 列添加索引:
create table a14 (id int primary key, age int);
insert into a14 value(1, 10),(2, 10);
create index age on a14(id, age);
mysql> explain select age from a14;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows |  Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1  | SIMPLE      | a14   | NULL       | index|     NULL        | age |       9    | NULL| 2     |Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
現在索引 age 中也包含了 age 列的值,因此不用訪問表便能返回結果了。
  • 4 using temporary :mysql對查詢結果進行排序的時候使用了一張臨時表。
mysql> EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did group by p.dept_id ORDER BY p.dept_id;
+----+-------------+-------+--------+---------------+---------+---------+------------+------+---------------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref        | rows | Extra                           |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+---------------------------------+
|  1 | SIMPLE      | p     | ALL    | NULL          | NULL    | NULL    | NULL       |    8 | Using temporary; Using filesort |
|  1 | SIMPLE      | d     | eq_ref | PRIMARY       | PRIMARY | 4       | test.p.dept_id| 1 | Using where; Using index        |

我們發現在執行這條SQL語句時出現了 using temporary,我們再來看看下面這條SQL語句,去掉 條件中 group by 分組

mysql> EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did ORDER BY p.dept_id;
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref        | rows | Extra                    |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+
|  1 | SIMPLE      | p     | ALL    | NULL          | NULL    | NULL    | NULL       |    8 | Using filesort           |
|  1 | SIMPLE      | d     | eq_ref | PRIMARY       | PRIMARY | 4       | test.p.dept_id|1  | Using where; Using index |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+

而為什麽第一個用了臨時表,而第二個沒有用呢?
因為如果有GROUP BY子句,或者如果GROUP BY中的字段都來自其他的表而非連接順序中的第一個表的話,就會創建一個臨時表了。

那麽如何解決呢?
咱們為group by 字段添加一個索引 

mysql> alter table person add index did_idx(dept_id);
Query OK, 0 rows affected

mysql> EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did group by p.dept_id ORDER BY p.dept_id;
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref        | rows | Extra                    |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+
|  1 | SIMPLE      | p     | index  | NULL          | did_idx | 5       | NULL       |    8 | Using index              |
|  1 | SIMPLE      | d     | eq_ref | PRIMARY       | PRIMARY | 4       | test.p.dept_id| 1 | Using where; Using index |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+

為什麽添加個索引就不會創建臨時表了呢? 原因就在於 SQL查詢時優先在索引樹中執行,如果索引樹滿足不了當前SQL,才會進行數據表查詢,那麽現在加了索引,
已經可以滿足查詢條件了,就沒有必要創建臨時表了
  • 5 using filesort: mysql對數據不是按照表內的索引順序進行讀取,而是使用了其他字段重新排序.
mysql> EXPLAIN select * from person ORDER BY id;
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+
|  1 | SIMPLE      | person | index | NULL          | PRIMARY | 4       | NULL |    8 |       |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+
如果我們用聚合主鍵進行排序,則Extra 為null,我們知道在innodb引擎中,主鍵為聚合索引,插入數據就會排好順序.最後說明mysql是按照表內的索引順序進行讀的

再看下面的列子:
mysql> EXPLAIN select * from person ORDER BY salary;
+----+-------------+--------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+--------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | person | ALL  | NULL          | NULL | NULL    | NULL |    8 | Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+------+----------------+
我們使用非主鍵字段進行排序,這是mysql就不能按照表內的索引順序進行讀了.需要讀取數據行後再進行排序處理
  • 6 using where: 表示 MySQL 服務器從存儲引擎收到查詢數據,再進行“後過濾”(Post-filter)。所謂“後過濾”,就是先讀取整行數據,再檢查此行是否符合 where 句的條件,符合就留下,不符合便丟棄。因為檢查是在讀取行後才進行的,所以稱為“後過濾”。
建表及插入數據:
create table a16 (num_a int not null, num_b int not null, key(num_a));
insert into a16 value(1,1),(1,2),(2,1),(2,2);
mysql> explain select * from a16 where num_a=1;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| 1  | SIMPLE      | a16   | NULL       | ref  | num_a         | num_a | 4        | const| 2     |  NULL |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+

雖然查詢中有 where 子句,但只有 num_a=1 一個條件,且 num_a 列存在索引,通過索引便能確定返回的行,無需進行“後過濾”。
所以,並非帶 WHERE 子句就會顯示"Using where"的。
mysql> explain select * from a16 where num_a=1 and num_b=1;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
| 1  | SIMPLE      | a16   | NULL       | ref  | num_a            | num_a | 4     | const | 2  | Using where |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+

此查詢增加了條件 num_b=1 ,此列沒有索引,但可以看到查詢同樣能使用 num_a 索引。 MySQL 先通過索引 num_a 找到 num_a=1 的行,然後讀取整行數據,
再檢查 num_b 是否等於 1 ,執行過程看上去象這樣:

num_a索引|num_b 沒有索引,屬於行數據
+-------+-------+
| num_a | num_b | where 子句(num_b=1)
+-------+-------+
| 1 | 1 | 符合
| 1 | 2 | 不符合
| ... | ... | ...
+-------+-------+
  • 詳情參考官方文檔: https://dev.mysql.com/doc/refman/5.6/en/using-explain.html

12.12 EXPLAIN結果中哪些信息要引起關註

  • 使用EXPLAIN解析SQL執行計劃時,如果有下面幾種情況,就需要特別關註下了:
    • 首先看下 type 這列的結果,如果有類型是 ALL 時,表示預計會進行全表掃描(full table scan)。通常全表掃描的代價是比較大的,建議創建適當的索引,通過索引檢索避免全表掃描。
    • 再來看下 Extra 列的結果,如果有出現 Using temporary 或者 Using filesort 則要多加關註:
    • Using temporary,表示需要創建臨時表以滿足需求,通常是因為GROUP BY的列沒有索引,或者GROUP BY和ORDER BY的列不一樣,也需要創建臨時表,建議添加適當的索引。
    • Using filesort,表示無法利用索引完成排序,也有可能是因為多表連接時,排序字段不是驅動表中的字段,因此也沒辦法利用索引完成排序,建議添加適當的索引。
    • Using where,通常是因為全表掃描或全索引掃描時(type 列顯示為 ALL 或 index),又加上了WHERE條件,建議添加適當的索引。
  • 其他狀態例如:Using index、Using index condition、Using index for group-by 則都還好,不用緊張。

  • 轉自:http://www.cnblogs.com/wangfengming/articles/8092914.html
  • 轉自:http://www.cnblogs.com/wangfengming/articles/8275448.html

MySQL - - 索引原理與慢查詢優化