SQL優化如何才能hold住

1、吹吹水

前幾天老大問我怎麼進行sql優化的,我回答了新建索引。哈哈哈,然後老大就出去找棍子了,進來之後跟我說你知道門在哪邊吧,自己出去還是我請你出去? 然後被迫出去捱打,回來之後老大說去看下什麼是Explain,然後交一份5000字檢討上來。。。。。。

以下內容以MySQL 8.0進行描述

2、基礎內容

既然想優化sql,那麼新建索引也確實沒錯,只不過不能看見一個字段就建一個索引,這樣就確實容易捱打,那先說下新建索引到底mysql做了什麼操作:
首先InnoDB的索引模型是B+樹,在InnoDB中,表都是根據主鍵順序以索引的形式存放的,這種存儲方式的表稱爲索引組織表。而每新建一個索引,就在InnoDB裏面對應一棵B+樹。假設,我們有一個主鍵列爲id的表,表中有字段k、name,並且在k上有索引。

create table T(
id int primary key
int not null
name varchar(16),
index (k))engine=InnoDB;

往表中插入三條數據:

INSERT INTO T (id, k) VALUES (1001);
INSERT INTO T (id, k) VALUES (2002);
INSERT INTO T (id, k) VALUES (3003);
INSERT INTO T (id, k) VALUES (5005);
INSERT INTO T (id, k) VALUES (6006);

兩棵樹的示例示意圖如下: 從圖中可以看出,每新建一個索引,就新加了一顆B+樹,而索引又分爲主鍵索引非主鍵索引
主鍵索引:主鍵索引的葉子節點存的是整行數據。在InnoDB裏,主鍵索引也被稱爲聚簇索引(clustered index)。
非主鍵索引:非主鍵索引的葉子節點內容是主鍵的值。在InnoDB裏,非主鍵索引也被稱爲二級索引(secondary index)。

那麼現在執行一條查詢語句:

select * from T where id=500;

這條語句只需要搜索id這棵B+樹,然後就會返回葉子節點的所有數據。

下一條查詢語句:

select * from T where k=5

這條語句首先需要去索引k的這顆B+樹,然後查到了k=5對應的主鍵id是500,在通過這個主鍵id再去id索引樹查詢一次然後返回數據,這個來回的過程就叫做回表

也就是說,基於非主鍵索引的查詢需要多掃描一棵索引樹。因此,我們在應用中應該儘量使用主鍵查詢。

當然,我也知道,在實際開發中,很少能用到主鍵查詢的,因爲主鍵一般不存在於業務流程中,那麼再來看下下面這個語句:

select id from T where k=5

這條語句和上面的語句最大的區別就是上面語句查詢的是所有字段,而下面這個語句查詢的只有id,而k字段索引樹葉子節點保存的就是id值,就可以直接返回,不用再回表查詢id索引樹了,這就叫做覆蓋索引

  • 由於覆蓋索引可以減少樹的搜索次數,顯著提升查詢性能,所以使用覆蓋索引是一個常用的性能優化手段。

基於上面覆蓋索引的說明,再來看一個例子:

CREATE TABLE `tuser` (
  `id` int(11NOT NULL,
  `id_card` varchar(32DEFAULT NULL,
  `name` varchar(32DEFAULT NULL,
  `age` int(11DEFAULT NULL,
  `ismale` tinyint(1DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `id_card` (`id_card`),
  KEY `name_age` (`name`,`age`)
ENGINE=InnoDB

建表語句中可以看到,有個主鍵索引id,有兩個二級索引id_card和name_age,而name_age是由兩個字段組成的索引(也可以是多個字段),叫做組合索引。假設現在要執行如下語句:

select id from tuser where name like ‘張%’;

上面這條查詢語句,雖然沒有在name字段添加單獨索引,但是他有個組合索引,所以上面這條語句也是能用到索引的: 可以看到,索引項是按照索引定義裏面出現的字段順序排序的,上面這條語句可以用上這個組合索引,查找到第一個符合條件的記錄是ID3,然後向後遍歷,直到不滿足條件爲止。

但是如果組合索引定義爲name_age (age,name)這樣的話,上面這條查詢語句就用不上這個組合索引了,這就叫做最左前綴原則

  • 單表索引不能過多,業界有個不成文的規定,單表字段不超過20個,索引不超過5個,因爲隨着數據量的增加,過多的索引會佔據很多物理空間。

當然,這些都是些基礎sql,遇到一些比較複雜的sql語句,怎麼優化呢,這就要用到Explain執行計劃了,先來看下舉例吧(這條sql語句很複雜,是現在公司用到的,所以打個碼,不好意思哈): 重點在這裏: 通過Explain關鍵字,可以看出來每個查詢掃描的行數,用到了哪個索引等等,下面文章,會講每個字段的意思,然後看下如何優化。。。。。。

3、Explain執行計劃詳解

就拿上面的圖片來說,Explain主要通過以下字段來顯示有關優化器的期望如何與實際執行相匹配的時間以及基於迭代器的其他信息:

id

id是執行順序,就是每條語句的執行優先級,有可能相同(這種情況就由優化器決定),也有可能不同(id值越大優先級越高,越先被執行)。

select_type

表示 select 查詢的類型,主要是用於區分各種複雜的查詢,像普通查詢、聯合查詢、子查詢等,值主要有以下幾點:

  • SIMPLE:表示最簡單的 select 查詢語句,也就是在查詢中不包含子查詢或者union取並集等操作。
  • PRIMARY:當查詢語句中包含任何複雜的子部分,最外層查詢則被標記爲PRIMARY。
  • SUBQUERY:當 select 或 where 列表中包含了子查詢,該子查詢被標記爲:SUBQUERY 。
  • DERIVED:表示包含在from子句中的子查詢的select,from列表中包含的子查詢會被標記爲derived 。
  • UNION:如果union後邊又出現的select語句,則會被標記爲union;若union包含在from子句的子查詢中,外層select 將被標記爲derived。
  • UNION RESULT:代表從union的臨時表中讀取數據,而table列的<union1,4>表示用第一個和第四個select的結果進行union操作。

table

查詢的表名,並不一定是真實存在的表,如果給表指定了別名,則顯示別名,也可能爲臨時表。(只是個表名,就註釋一下)

partitions

查詢時匹配到的分區信息,對於非分區表值爲NULL,當查詢的是分區表時,partitions顯示分區表命中的分區情況。

type

查詢使用了何種類型,它在 SQL優化中是一個非常重要的指標,以下性能從好到壞依次是:system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

  • system: 當表僅有一行記錄時(系統表),數據量很少,往往不需要進行磁盤IO,速度非常快。
  • const:表示查詢時命中 primary key 主鍵或者 unique 唯一索引,或者被連接的部分是一個常量(const)值。這類掃描效率極高,返回數據量少,速度非常快。
  • eq_ref:查詢時命中主鍵primary key 或者 unique key索引。
  • ref:區別於eq_ref ,ref表示使用非唯一性索引,會找到很多個符合條件的行。
  • ref_or_null:這種連接類型類似於 ref,區別在於 MySQL會額外搜索包含NULL值的行。
  • index_merge:使用了索引合併優化方法,查詢使用了兩個以上的索引。
  • unique_subquery:只是用來完全替換子查詢的索引查找函數效率更高了。
  • index_subquery:區別於unique_subquery,用於非唯一索引,可以返回重複值。
  • range:使用索引選擇行,僅檢索給定範圍內的行。簡單點說就是針對一個有索引的字段,給定範圍檢索數據。在where語句中使用 bettween...and、<、>、<=、in 等條件查詢 type 都是 range。
  • index:Index 與ALL 其實都是讀全表,區別在於index是遍歷索引樹讀取,而ALL是從硬盤中讀取。
  • ALL:將遍歷全表以找到匹配的行,性能最差。

possible_keys

表示在MySQL中通過哪些索引,能讓我們在表中找到想要的記錄,一旦查詢涉及到的某個字段上存在索引,則索引將被列出,但這個索引並不一定是最終查詢數據時所被用到的索引。((有時候,你雖然給相應字段建立了索引,但是優化器並不一定會按這個索引來執行,這時候就需要你以force index來指定))

key

區別於possible_keys,key是查詢中實際使用到的索引,若沒有使用索引,顯示爲NULL。

key_len

key_len:表示查詢用到的索引長度(字節數),原則上長度越短越好

單列索引就將整個索引長度算進去,多列索引,不是所有列都能用到,需要計算查詢中實際用到的列。

ref

ref:常見的有:const,func,null,字段名

當使用常量等值查詢,顯示const。當關聯查詢時,會顯示相應關聯表的關聯字段。如果查詢條件使用了表達式、函數,或者條件列發生內部隱式轉換,可能顯示爲func。其他情況都顯示爲null。

rows

rows:以表的統計信息和索引使用情況,估算要找到我們所需的記錄,需要讀取的行數。

這是評估SQL性能的一個比較重要的數據,mysql需要掃描的行數,很直觀的顯示SQL性能的好壞,一般情況下rows值越小越好。

filtered

filtered 這個是一個百分比的值。簡單點說,這個字段表示存儲引擎返回的數據在經過過濾後,剩下滿足條件的記錄數量的比例。(MySQL.5.7後,默認explain直接顯示partitions和filtered的信息)。

Extra

這個列會顯示很多不適合在其他列中顯示的信息,Explain 中的很多額外的信息會在這個字段顯示:

  • Using index:我們在相應的 select 操作中使用了覆蓋索引,通俗一點講就是查詢的列被索引覆蓋,使用到覆蓋索引查詢速度會非常快,SQl優化中理想的狀態。
EXPLAIN SELECT id FROM s_goods;
image
image
  • Using where:查詢時未找到可用的索引,進而通過where條件過濾獲取所需數據,但要注意的是並不是所有帶where語句的查詢都會顯示Using where。
EXPLAIN SELECT * FROM s_goods WHERE cat_id = 1;
image
image
  • Using temporary:表示查詢後結果需要使用臨時表來存儲,一般在排序或者分組查詢時用到。
EXPLAIN SELECT shop_id FROM s_goods WHERE cat_id IN (1,2,3,4GROUP BY shop_id;
image
image
  • Using filesort:表示無法利用索引完成的排序操作,也就是ORDER BY的字段沒有索引,通常這樣的SQL都是需要優化的。
EXPLAIN SELECT * FROM s_goods ORDER BY shop_id;
image
image
  • Using join buffer:關聯字段沒有用到索引的話,會顯示這個。
EXPLAIN SELECT * FROM s_goods a LEFT JOIN s_goods_cats b ON a.shop_id = b.parent_id;
image
image
  • Impossible where:表示在我們用不太正確的where語句,導致沒有符合條件的行。
EXPLAIN SELECT * FROM s_goods WHERE 1 <> 1;
image
image
  • No tables used:我們的查詢語句中沒有FROM子句,或者有 FROM DUAL子句。
EXPLAIN SELECT NOW();
image
image

還有一些其他的字段,這裏就不再一一列舉了,如果有需要可以點擊下面鏈接查看!

最後附上MYSQL 8.0文獻資料地址,有疑問可以點擊查詢!

鳴謝

結尾

如果你覺得我的文章對你有幫助話,歡迎關注我的微信公衆號:"一個快樂又痛苦的程序員"(無廣告,單純分享原創文章、已pj的實用工具、各種Java學習資源,期待與你共同進步)

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