SQL執行計劃解讀
聲明
- 5.6中desc看不到show warnings,也看不到filtered列
- 5.7的desc等於5.6的desc extended,這樣可以看show warnings,5.6中filtered列非常不準,5.7好一些
先看一個執行計劃
(root@localhost) [test]> desc select * from l; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
Ⅰ、展開分析每個字段
id列——表示sql執行的順序
- id相等,一般是簡單關聯,從上往下看即可
- id不相等,一般為出現了子查詢,先看大的再看小的
- 同時存在相等與不相等,一般相等的為一組,先看大的再看小的
有個潛規則叫:id相等從上往下看,id不等從下往上看
select_type——select類型,用於區分子查詢,關聯查詢等
- SIMPLE:簡單查詢,不包含子查詢與union
- PRIMARY:查詢中包含子查詢,最外層查詢則被標記為PRIMARY
- UNION:使用union連接select時,從第二個select開始都是UNION
- SUBQUERY:select或者where後面的子查詢(非from之後)都可能是SUBQUERY
- DERIVED:在from中包含的子查詢被標記為DERIVED(派生表)
- DEPENDENTSUBQUERY:依賴外部查詢的SUBQUERY
- UNION RESULT:UNION的結果,對應ID為NULL
table——輸出記錄的表
- 查詢中使用別名,則此處顯示別名
- 不涉及表的操作,則顯示為NULL
- < derivedN > / < subqueryN > 由ID為N的查詢產生的結果
- < unionM,N > 由ID為M,N查詢union產生的結果集
type——訪問類型
- system:const的特例,表中只有一行記錄
- const:使用唯一索引或主鍵,只取一行數據
- eq_ref:多表join時,驅動表只返回一行數據,且這行數據是被驅動表的主鍵或唯一索引,且必須not null
- ref:非唯一索引的掃描,通常為非唯一索引的等值查詢
- range:檢索給定範圍的行,使用一個索引來選擇行,通常為where條件中出現between、<、>、in、like等
- index:full index scan,根據索引讀全表
- ALL:full table scan,掃整個數據文件
- fulltext:全文索引
- ref_or_null:使用普通索引進行查詢,但要查詢null值
- index_merge or:查詢會使用到的類型,可能一條sql使用了兩個索引,然後merge
unique_subquery和index_subquery:很少出現 前一個是子查詢的列是唯一索引,第二個是子查詢的列是普通索引
主要優化對象是index和ALL,有兩種情況可以考慮保留index
只查詢索引列,不回表或者使用索引進行排序或者聚合
possible_keys
優化器可能使用到的索引
key
優化器實際選擇的索引
key_len
使用索引的字節長度
ref
- 等值查詢會顯示const
- 連接查詢的話被驅動表此處顯示驅動表的join列
rows
優化器預估的記錄數量
filtered
根據條件過濾得到的記錄的百分比
extra
- Using index:優化器只需使用索引就能得到結果 索引覆蓋
- Using index condition:優化器試用index condition pushdown優化,二級索引
- Using index for group by:優化器只需使用索引就能處理group by 或者distinct語句
上面這3個基本上忽略吧,沒什麽參數好調的 - Using temporary:使用臨時表,常見於order by,group by
- Using filesort:使用額外的排序 調整sort_buffer_size
- Using join buffer:優化器需要使用join buffer 調整join_buffer_size
- Using MRR:優化器使用MRR優化 調整read_cache_size
- Using temporary:優化器需要使用臨時表 調整tmp_table_size
- Using where:優化器使用where過濾
Ⅱ、分析兩個執行計劃看看
案例1
(root@localhost) [dbt3]> DESC SELECT
-> *
-> FROM
-> part
-> WHERE
-> p_partkey IN (SELECT
-> l_partkey
-> FROM
-> lineitem
-> WHERE
-> l_shipdate BETWEEN ‘1997-01-01‘ AND ‘1997-02-01‘)
-> ORDER BY p_retailprice DESC
-> LIMIT 10;
+----+--------------+-------------+------------+--------+----------------------------------------------+--------------+---------+---------------------+--------+----------+----------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+--------+----------------------------------------------+--------------+---------+---------------------+--------+----------+----------------------------------+
| 1 | SIMPLE | part | NULL | ALL | PRIMARY | NULL | NULL | NULL | 197706 | 100.00 | Using where; Using filesort |
| 1 | SIMPLE | <subquery2> | NULL | eq_ref | <auto_key> | <auto_key> | 5 | dbt3.part.p_partkey | 1 | 100.00 | NULL |
| 2 | MATERIALIZED | lineitem | NULL | range | i_l_shipdate,i_l_suppkey_partkey,i_l_partkey | i_l_shipdate | 4 | NULL | 138672 | 100.00 | Using index condition; Using MRR |
+----+--------------+-------------+------------+--------+----------------------------------------------+--------------+---------+---------------------+--------+----------+----------------------------------+
3 rows in set, 1 warning (0.01 sec)
id 順序
1 ② part表(外表)和subquery2(id=2產生的14w記錄的表)進行關聯,對於part表中所有記錄都要關聯,一共是19w行,再和l_partkey進行關聯,最後排序用到using filesort
1 ③ 內表要加索引,所以mysql優化器自動把第一步取出來的數據添加了一個唯一索引,in裏面是去重的(這其實是做了一個物化),所以是唯一索引,eq_ref表示通過唯一索引進行關聯,和外表中的p_partkey關聯
2 ① 先查lineitem表,是一個range範圍查詢,使用了i_l_shipdate索引,l_shipdate是date類型,占用四個字節,預估14萬行記錄,過濾出百分之百,materiallized表示產生了一張實際的表,並且去添加了索引,l_partkey,唯一索引(in裏面是去重的)
註意一個細節
(root@localhost) [dbt3]> DESC SELECT
-> *
-> FROM
-> part
-> WHERE
-> p_partkey IN (SELECT
-> l_partkey
-> FROM
-> lineitem
-> WHERE
-> l_shipdate BETWEEN ‘1997-01-01‘ AND ‘1997-01-07‘)
-> ORDER BY p_retailprice DESC
-> LIMIT 10;
+----+--------------+-------------+------------+--------+----------------------------------------------+--------------+---------+-----------------------+-------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+--------+----------------------------------------------+--------------+---------+-----------------------+-------+----------+----------------------------------------------+
| 1 | SIMPLE | <subquery2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100.00 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | part | NULL | eq_ref | PRIMARY | PRIMARY | 4 | <subquery2>.l_partkey | 1 | 100.00 | NULL |
| 2 | MATERIALIZED | lineitem | NULL | range | i_l_shipdate,i_l_suppkey_partkey,i_l_partkey | i_l_shipdate | 4 | NULL | 29148 | 100.00 | Using index condition; Using MRR |
+----+--------------+-------------+------------+--------+----------------------------------------------+--------------+---------+-----------------------+-------+----------+----------------------------------------------+
3 rows in set, 1 warning (0.00 sec)
驅動表就變成了subquerry2,這時候優化器又把子查詢作為了外表,說明優化器很聰明
in的子查詢,優化器會幫你重寫成join,並且幫你選擇子查詢到底是內表還是外表
(root@localhost) [dbt3]> DESC select
-> a.*
-> from
-> part a,
-> (select distinct
-> l_partkey
-> from
-> lineitem
-> where l_shipdate between ‘1997-01-01‘ and ‘1997-02-01‘) b
-> where
-> a.p_partkey=b.l_partkey
-> order by a.p_retailprice desc
-> limit 10;
+----+-------------+------------+------------+--------+----------------------------------------------+--------------+---------+-------------+--------+----------+---------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+----------------------------------------------+--------------+---------+-------------+--------+----------+---------------------------------------------------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 138672 | 100.00 | Using where; Using temporary; Using filesort |
| 1 | PRIMARY | a | NULL | eq_ref | PRIMARY | PRIMARY | 4 | b.l_partkey | 1 | 100.00 | NULL |
| 2 | DERIVED | lineitem | NULL | range | i_l_shipdate,i_l_suppkey_partkey,i_l_partkey | i_l_shipdate | 4 | NULL | 138672 | 100.00 | Using index condition; Using MRR; Using temporary |
+----+-------------+------------+------------+--------+----------------------------------------------+--------------+---------+-------------+--------+----------+---------------------------------------------------+
3 rows in set, 1 warning (0.00 sec)
這麽改寫,b表永遠是外表,子查詢只是產生一個派生表,但是沒辦法給它建索引,如果子查詢出來的結果集很大,這時候性能就不如in了,in的話優化器會把它作為內表
案例2
(root@localhost) [dbt3]> DESC select max(l_extendedprice)
-> from orders,lineitem
-> where o_orderdate between ‘1995-01-01‘ and ‘1995-01-31‘
-> and l_orderkey=o_orderkey;
+----+-------------+----------+------------+-------+--------------------------------------------+---------------+---------+------------------------+-------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+--------------------------------------------+---------------+---------+------------------------+-------+----------+--------------------------+
| 1 | SIMPLE | orders | NULL | range | PRIMARY,i_o_orderdate | i_o_orderdate | 4 | NULL | 40696 | 100.00 | Using where; Using index |
| 1 | SIMPLE | lineitem | NULL | ref | PRIMARY,i_l_orderkey,i_l_orderkey_quantity | PRIMARY | 4 | dbt3.orders.o_orderkey | 3 | 100.00 | NULL |
+----+-------------+----------+------------+-------+--------------------------------------------+---------------+---------+------------------------+-------+----------+--------------------------+
2 rows in set, 1 warning (0.00 sec)
orderkey上有索引,但是沒用,用的是pk,orders表示外表,根據過濾條件把數據過濾出來做外表,然後跟lineitem表關聯,用的是pk,關聯的列是orders.o_orderkey
如果強行走orderkey索引,成本很高,需要回表,通過主鍵不用回表
案例3
(root@localhost) [dbt3]> DESC select *
-> from
-> lineitem
-> where
-> l_shipdate <= ‘1995-12-32‘
-> union
-> select
-> *
-> from
-> lineitem
-> where
-> l_shipdate >= ‘1997-01-01‘;
+----+--------------+------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+
| 1 | PRIMARY | lineitem | NULL | ALL | i_l_shipdate | NULL | NULL | NULL | 5409799 | 33.33 | Using where |
| 2 | UNION | lineitem | NULL | ALL | i_l_shipdate | NULL | NULL | NULL | 5409799 | 50.00 | Using where |
|NULL| UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+
3 rows in set, 3 warnings (0.10 sec)
union result合並兩張表 會using temporary,使用臨時表,union會去重,所以又去建了臨時表,在上面加了唯一索引,這裏就用了兩個索引,所以一個sql只能用一條索引是不對的
案例4
(root@localhost) [employees]> DESC SELECT
-> emp_no,
-> dept_no,
-> (SELECT
-> COUNT(1)
-> FROM
-> dept_emp t2
-> WHERE
-> t1.emp_no <= t2.emp_no) AS row_num
-> FROM
-> dept_emp t1;
+----+--------------------+-------+------------+-------+----------------+--------+---------+------+--------+----------+------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-------+------------+-------+----------------+--------+---------+------+--------+----------+------------------------------------------------+
| 1 | PRIMARY | t1 | NULL | index | NULL | emp_no | 4 | NULL | 331570 | 100.00 | Using index |
| 2 | DEPENDENT SUBQUERY | t2 | NULL | ALL | PRIMARY,emp_no | NULL | NULL | NULL | 331570 | 33.33 | Range checked for each record (index map: 0x3) |
+----+--------------------+-------+------------+-------+----------------+--------+---------+------+--------+----------+------------------------------------------------+
2 rows in set, 2 warnings (0.00 sec)
對於這個sql,先執行了1再執行了2,2是dependent subquery,要依賴子查詢,所以先執行了1,所以t1是外表,t2是內表,每次得關聯33w * 33%次數,一共關聯33w次,一共是33w * 10w次
行號問題,性能非常差
SQL執行計劃解讀