阿里的OceanBase上天了,但你還不會用Explain看SQL的查詢計劃嗎?

Mysql性能優化神神器explain。一文通透

前言

SQL語句在不一樣的人手中會寫出不一樣的語句形式,好比常常遇到的SQL慢查詢,這時候每每須要針對SQL進行優化。
而Mysql中爲保證SQL語句可以高效的運行,提供了一個Explain的命令,用來對SQL語句進行語義分析,供開發者來針對SQL進行優化。
image.pngsql

數據準備

爲了方便整個流程的執行,首先建立好測試數據。數據庫

建立數據表

SQL中的執行涉及到單表與多表的聯合執行,本次建立兩張表用來模擬該狀況,更多的多表聯合執行與兩張表執行計劃是同樣的。緩存

CREATE TABLE `users` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶名',
    `sex` tinyint(4) NOT NULL DEFAULT '1' COMMENT '性別',
    `phone` varchar(11) NOT NULL COMMENT '手機號',
    `desc` varchar(200) NOT NULL DEFAULT '' COMMENT '介紹',
    primary key (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '用戶表';

CREATE TABLE `order`(
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `phone` varchar(11) NOT NULL COMMENT '手機號',
    `name` varchar(20) NOT NULL COMMENT '用戶名',
    primary key (`id`)
) ENGINE =InnoDB default CHARSET=utf8 COMMENT '訂單';

插入數據

爲了方便本次沒有使用SQL語句,而是使用存儲過程建立數據,簡單快速也方便。性能優化

# 建立 存儲過程
create procedure insert_user_data()
begin

declare i int ;
declare name varchar(20);
declare phone_num varchar(11);

set @SURNAME = '王李張劉陳楊黃趙吳周徐孫馬朱胡郭何高林羅鄭梁謝宋唐位許韓馮鄧曹彭曾蕭田董潘袁於蔣蔡餘杜葉程蘇魏呂丁任沈姚盧姜崔鍾譚陸汪範金石廖賈夏韋傅方白鄒孟熊秦邱江尹薛閻段雷侯龍史陶黎賀顧毛郝龔邵萬錢嚴覃武戴莫孔向湯';
set @NAME = '丹舉義之樂書乾雲亦從代以偉佑俊修健傲兒元光蘭冬冰冷凌凝凡凱初力勤千卉半華南博又友同向君聽和哲嘉國堅城夏夜天奇奧如妙子存季孤宇安宛宸寒尋爾堯山嵐峻巧平幼康建開弘強彤彥彬彭心憶志念懷憐恨惜慕成擎敏文新旋旭昊明易昕映春昱晉曉晗晟景晴智曼朋朗傑鬆楓柏柔柳格桃夢楷槐正水沛波澤潔洋濟浦浩海濤潤涵淵源溥濮瀚靈燦炎煙燁然煊煜熙熠玉珊珍理琪琴瑜瑞瑤瑾璞癡皓盼真睿碧磊祥祺秉程立竹笑紫紹經綠羣翠翰致航良芙芷蒼苑若茂榮蓮菡菱萱蓉藍蕊蕾薇蝶覓訪誠語谷豪賦超越軒輝達遠邃醉金鑫錦問雁雅雨雪霖霜露青靖靜風飛香馳騫高鴻鵬鶴黎';

set i =1;

while  i < 100000 do
    SET phone_num = concat('1',
                    substring(cast(3 + (rand() * 10) % 7 AS char(50)), 1, 1),
                    right(left(trim(cast(rand() AS char(50))), 11), 9));

    set name = concat(substr(@surname,floor(rand()*length(@surname)/3+1),1), substr(@NAME,floor(rand()*length(@NAME)/3+1),1), substr(@NAME,floor(rand()*length(@NAME)/3+1),1));
    insert into users(create_time,name, sex,phone ,`desc`) values(now(),name,rand()*1,phone_num ,'test');
    insert into `order`(phone,name) values(phone_num,name);

    set i=i+1;
end while;

end

# 執行存儲過程
call insert_user_data();
# 刪除存儲過程
drop  procedure if exists insert_user_data ;

建立存儲過程後,有須要修改就直接使用刪除存儲過程,再從新建立便可。服務器

explain命令使用

explain的執行命令explain select * from users where id =1 \G;展現以下:微信

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: users
   partitions: NULL
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

一共12個字段,各個字段的含義以下:oop

  • id: 每個查詢語句都會生成標識符,執行順序是id從大到小執行
  • select_type: 查詢的類型,裏面包含多種類型跳轉到select_type
  • table: 查詢的表名,包含關聯的表信息
  • partitions:匹配的分區
  • type: 表示Mysql在表中找到的所需行的方式,這裏是表示使用索引的方式。type
  • possible_keys: 查詢語句可能用到的索引
  • key: 查詢語句真正使用的索引
  • key_len: 表示索引中使用的字節數,注意顯示的是索引字段中最大可能長度,而不是實際使用長度
  • ref: 上述表的鏈接方式,哪些列或常量被用於查找索引列上的值
  • rows: 估算查找到所需記錄的須要讀取的行數
  • extra: 該列查詢中包含的其餘額外詳細信息。

有些字段有更多的類型,如下是詳細講解。

select_type

用來表示每一個查詢類型,經常使用類型以下:

  • SIMPLE: 最簡單的查詢方式,單表查詢,不包含UNION以及子查詢,例如select * from users where id =1
  • PRIMAPY: 表示次查詢是是最外層的查詢。有子查詢的時候展現。explain select * from users where phone=(select phone from order where id = 10);
  • UNION: 表示次查詢是UNION的第二或隨後查詢方式,查詢語句中存在union關鍵字explain select * from users where id = 10 union select * from users where id = 20;上傳圖片
  • DEPENDENT UNION:UNION中的第二個或後面的查詢語句,取決於外面的查詢
  • UNION RESULT,UNION的結果。看圖示
  • SUBQUERY: 子查詢中的第一個SELECT.子查詢方式
  • DEPENDENT SUBQUERY: 子查詢中的第一個第一個SELECT,取決於外面的查詢,當子查詢依賴外部的查詢結果時會有該內容展現explain select * from users where phone=(select phone from order where id = users.id ) and id =10;依賴外部

在這裏面最多見的類型就屬於SIMPLE類型,咱們常用的多表查詢也是SIMPLE類型。例如explain select * from users left join order o on users.phone = o.phone where users.id =10多表查詢是SIMPLE類型

type

type字段幫助咱們來定位查詢是否高效,是全表掃描仍是索引掃描。
不一樣的type,表明的性能不同,順序以下:
ALL < index < range ~ index_merge < ref < eq_ref < const < system

經常使用類型以下:

  • ALL: 全表掃描,當數據庫中的數據巨大時,一個查詢仍是使用全表掃描的方式,這個查詢對數據庫的壓力影響是巨大的,解決方式是經過添加索引來避免。explain select * from users;,能夠看到全表掃描掃了200多萬行的數據。全表掃描
  • Index:$\color{red}{全索引掃描}$,只掃描全部的索引,而不掃描數據,相對全表掃描來講已經下降部分數據量。同時在Extra字段顯示Index.explain select id from users;查詢語句中id是主鍵索引,則只查詢的是索引數據主鍵索引.
  • range:創建在索引的基礎上進行數據過濾查詢,這些能使用索引的標識符有=,<,>,<=,>=, BETWEEN,IN操做符中。explain select phone from users where id > 10 and id < 20 ; SQL語句中使用>和<來限定where條件使用的仍是range,$\color{red}{當語句中的字段不是索引時,則不是使用的range}$範圍查詢
  • ref: 查詢中使用非惟一索引查詢,同時在ref列顯示使用哪一個列或者常數。雖然使用了索引,但該索引列的值是能夠存在多個的,如phone列出現相同的手機號。explain select * from order where phone ='16485461071'ref
  • ref_eq:用法相似,但比ref好點的是,該類型是知道結果集只有一條。直接知道結果集是一條記錄的索引是主鍵索引與惟一索引,使用該類型是在多表查詢時,條件中包含主鍵或惟一索引的條件explain select * from users, order where users.id=order.idref_eq使用
  • const: 主鍵值做爲where的條件查詢,Mysql優化器會將此次查詢轉爲一個常量看待explain select * from order where id =10;常量查詢
  • system: const類型的一個特例,當表中只有一行數據時,會使用system類型

rows

查詢中所須要掃描的行數,咱們使用各類索引,優化都是爲了減小掃描的行數。

ref

表示在查詢時,表的鏈接匹配條件,能夠是常量,也能夠是查詢的列explain select * from users, order where users.id=order.id;
ref表示的關聯列或常量

extra

extra 表示更多的sql查詢信息,extra是Mysql查詢計劃中查詢信息重要補充。extra的類型以下:

  • Distinct: 在查找到第一行後,再也不進行匹配查找更多的數據,對應到查詢中的distinct去重查詢。
  • Using filesort: 表明MYSQL使用的是內存排序或者文件排序,而且該排序沒有使用到索引。可使用合適的索引來修改order by ,group by語句中的條件。
  • Using temporary: 使用臨時表保存中間結果,經常使用與Group by ,Order by語句查詢中。一樣的儘可能避免使用臨時表來保存中間結果。
  • Not exists: 在某些 LEFT JOIN 鏈接中,MYSQL使用優化器進行優化,改變原有的QUERY的組成優化部分,減小數據訪問的次數。
  • Using index: 查詢時不須要回表,直接經過索引就能夠得到查詢的數據。
  • Using union: 使用or鏈接各個索引條件時,代表信息表示從處理結果中獲取並集。
  • Using intersect: 使用and鏈接各個索引條件時,代表信息表示從處理結果中獲取並集。
  • Using sort_union/Using sort_intersection:出如今and/or語句中,先查詢主鍵信息,再將結果進行排序合併的數據讀取中。
  • Using where: 使用Where 字句來限制數據的返回,注意:使用Using Where表示是Mysql服務器將存儲引擎返回服務層後再進行條件過濾。
  • Using join buffer: 使用了鏈接緩存,一共兩種:塊嵌套循環鏈接Block Nested Loop,以及Index Nested-Loop Join使用索引查詢。

總結

明白SQL的查詢計劃,當再寫SQL時,多多使用explain語句來看下SQL的查詢計劃是怎樣的,心中對SQL的執行有大概的瞭解,方能得心運手。

本文中用到的SQL語句上傳到github中須要的自取
·END·

路雖遠,行則必至

本文原發於 同名微信公衆號「胖琪的升級之路」,回覆「1024」你懂得,給個讚唄。

微信ID:YoungRUIQ

公衆號

相關文章
相關標籤/搜索