1. 程式人生 > >SQL執行計劃解讀

SQL執行計劃解讀

ron 範圍 子查詢 等於 war from 查詢 需要 產生

聲明

  • 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執行計劃解讀