天天看點

explain的type列解析1、const2、ref3、eq_ref4、Ref_or_null5、range:6、index7、index_merge7.1 Intersection合并7.2 union合并7.3 sort-union

1、const

表示這個執行步驟最多隻傳回一行資料。const通常出現在對主鍵或唯一索引的等值查詢中,例如對t表主鍵id的等值查詢:

explain的type列解析1、const2、ref3、eq_ref4、Ref_or_null5、range:6、index7、index_merge7.1 Intersection合并7.2 union合并7.3 sort-union

可以認為通過主鍵或者唯一二級索引列與常數的等值比較來定位一條記錄是像坐火箭一樣快的,是以他們把這種通過主鍵或者唯一二級索引列來定位一條記錄的通路方法定義為:const,意思是常數級别的,代價是可以忽略不計的。不過這種const通路方法隻能在主鍵列或者唯一二級索引列和一個常數進行等值比較時才有效。

如果主鍵或者唯一二級索引是由多個列構成的話,索引中的每一個列都需要與常數進行等值比較,這個const通路方法才有效(這是因為隻有該索引中全部列都采用等值比較才可以定位唯一的一條記錄)。比如主鍵由(a,b,c)組成,那麼隻有“where a= and b= and c=**”,執行計劃的type列才會出現const。

另外,對于唯一二級索引來說,查詢該列為NULL值的情況比較特殊,比如這樣:

SELECT * FROM single_table WHERE key2 IS NULL;           

因為唯一二級索引列并不限制 NULL 值的數量,是以上述語句可能通路到多條記錄,也就是說上邊這個語句不可以使用const通路方法來執行。

另外需要說明的是,在阿裡java開發規範手冊中有看到:

“consts 表示單表中最多隻有一個比對行(主鍵或者唯一索引),在優化階段即可讀取到資料”           

在優化階段就會讀取到資料,下面的例子中可以展現:

mysql> explain select num,created_time from t_operater_record where id=493490480924;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                          |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
|  1 | SIMPLE      | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | no matching row in const table |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select version();
+------------+
| version()  |
+------------+
| 5.7.22-log |
+------------+           

id是主鍵,沒有493490480924這個值。extra中顯示 在const table中沒有比對的行,顯然這裡已經讀取到資料了。

2、ref

有時候我們對某個普通的二級索引列與常數進行等值比較,比如這樣:

mysql> explain select * from t_operater_record where updated_time='2010-10-23 07:17:27';
+----+-------------+-------------------+------------+------+----------------+----------------+---------+-------+------+----------+-------+
| id | select_type | table             | partitions | type | possible_keys  | key            | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------------------+------------+------+----------------+----------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t_operater_record | NULL       | ref  | i_updated_time | i_updated_time | 5       | const |    3 |   100.00 | NULL  |
+----+-------------+-------------------+------------+------+----------------+----------------+---------+-------+------+----------+-------+           

MySQL把這種搜尋條件為二級索引列與常數等值比較,采用二級索引來執行查詢的通路方法稱為:ref。

對于普通的二級索引來說,通過索引列進行等值比較後可能比對到多條連續的記錄,而不是像主鍵或者唯一二級索引那樣最多隻能比對1條記錄,是以ref通路方法比const差了那麼一丢丢,但是在二級索引等值比較時比對的記錄數較少時的效率還是很高的。這種通路表的效率就跟坐高鐵差不多。

另外,需要注意下面的兩種情況:

a.二級索引列值為NULL的情況:不論是普通的二級索引,還是唯一二級索引,它們的索引列對包含NULL值的數量并不限制,是以我們采用key IS NULL這種形式的搜尋條件最多隻能使用ref的通路方法,而不是const的通路方法。

b.對于多列二級索引來說,隻要是最左邊的連續索引列是與常數的等值比較就可能采用ref的通路方法,比方說下邊這幾個查詢:

SELECT * FROM single_table WHERE key_part1 = 'god like';
SELECT * FROM single_table WHERE key_part1 = 'god like' AND key_part2 = 'legendary';
SELECT * FROM single_table WHERE key_part1 = 'god like' AND key_part2 = 'legendary' AND key_part3 = 'penta kill';           

但是如果最左邊的連續索引列并不全部是等值比較的話,它的通路方法就不能稱為ref了,比方說這樣:

SELECT * FROM single_table WHERE key_part1 = 'god like' AND key_part2 > 'legendary';           

3、eq_ref

eq_ref類型一般意味着在表關聯時,被關聯表上的關聯列走的是主鍵或者唯一索引。

例如,表jiang關聯lock_test表,關聯列分别是兩張表的主鍵列 :

explain的type列解析1、const2、ref3、eq_ref4、Ref_or_null5、range:6、index7、index_merge7.1 Intersection合并7.2 union合并7.3 sort-union

上面SQL執行時,jiang表是驅動表,lock_test是被驅動表,被驅動表的關聯列是主鍵id,type類型為eq_ref。

MySQL把在連接配接查詢中對被驅動表使用主鍵值或者唯一二級索引列的值進行等值查找的查詢執行方式稱之為:eq_ref。

4、Ref_or_null

例如執行下面語句:

select * from lock_test where num=110 or num is null;           

表示走了索引(num列上有索引),但是也通路了空值。當使用二級索引等值查詢且同時查詢這個二級索引的null值時,就可能會出現ref_or_null。

5、range:

前面介紹的const、ref、ref_or_null都是在對索引列與某一個常數進行等值比較的時候才可能出現的,但是有時候我們面對的搜尋條件會比這些更複雜些,比如下邊查詢:

mysql> explain select * from t_operater_record where updated_time between '2010-10-23 07:17:27' and '2010-12-23 07:17:27';
mysql> explain select * from t_operater_record where updated_time='2010-10-23 07:17:27' or updated_time='2004-05-24 20:36:50';           

以上兩個查詢都是對索引列的範圍查詢,兩個查詢都可以走二級索引+回表的方式通路資料。MySQL把這種利用索引進行範圍比對的通路方法稱之為:range。(此處所說的使用索引進行範圍比對中的

索引

可以是聚簇索引,也可以是二級索引)。

6、index

假設表single_table有聯合索引(key_part1, key_part2, key_part3), 看下邊這個查詢:

SELECT key_part1, key_part2, key_part3 FROM single_table WHERE key_part2 = 'abc';           

由于key_part2并不是聯合索引idx_key_part最左索引列,是以我們無法使用ref或者range通路方法來執行這個語句。但是這個查詢符合下邊這兩個條件:

它的查詢清單隻有3個列:key_part1, key_part2, key_part3,而索引idx_key_part又包含這三個列。

搜尋條件中隻有key_part2列。這個列也包含在索引idx_key_part中。

也就是說我們可以直接通過周遊idx_key_part索引的葉子節點的記錄來比較key_part2 = 'abc'這個條件是否成立,把比對成功的二級索引記錄的key_part1, key_part2, key_part3列的值直接加到結果集中就行了。由于二級索引記錄比聚簇索記錄小的多(聚簇索引記錄要存儲所有使用者定義的列以及所謂的隐藏列,而二級索引記錄隻需要存放索引列和主鍵),而且這個過程也不用進行回表操作,是以直接周遊二級索引比直接周遊聚簇索引的成本要小很多,MySQL就把這種采用周遊二級索引記錄的執行方式稱之為:index。

比如:t_operater_record表有聯合索引(num,status,created_time),下面的查詢中type顯示index:

mysql> explain select num,created_time from t_operater_record where status in(33,43);
+----+-------------+-------------------+------------+-------+---------------+---------------------------+---------+------+---------+----------+--------------------------+
| id | select_type | table             | partitions | type  | possible_keys | key                       | key_len | ref  | rows    | filtered | Extra                    |
+----+-------------+-------------------+------------+-------+---------------+---------------------------+---------+------+---------+----------+--------------------------+
|  1 | SIMPLE      | t_operater_record | NULL       | index | NULL          | i_num_status_created_time | 11      | NULL | 1990233 |    20.00 | Using where; Using index |
+----+-------------+-------------------+------------+-------+---------------+---------------------------+---------+------+---------+----------+--------------------------+
1 row in set, 1 warning (0.01 sec)           

7、index_merge

絕大多數時候,MySQL在一般情況下執行一個查詢時最多隻會用到單個二級索引。但不是還有特殊情況麼,在這些特殊情況下也可能在一個查詢中使用到多個二級索引,MySQL把這種使用到多個索引來完成一次查詢的執行方法稱之為:index merge,具體的索引合并算法有下邊三種。

7.1 Intersection合并

Intersection翻譯過來的意思是交集。這裡是說某個查詢可以使用多個二級索引,将從多個二級索引中查詢到的結果取交集。

SELECT * FROM single_table WHERE key1 = 'a' AND key3 = 'b';           

比如對于上面這個查詢,mysql可能會走key1或key3單個索引通路表資料,也可能采用intersection索引合并的方式通路表資料,具體采用哪種方式是由估算的成本決定的。

MySQL在某些特定的情況下才可能會使用到Intersection索引合并:

情況一:

二級索引列是等值比對的情況。如果二級索引是聯合索引,那麼在聯合索引中的每個列都必須等值比對,不能出現隻比對部分列的情況。

比方說下邊這個查詢可能用到idx_key1和idx_key_part這兩個二級索引進行Intersection索引合并的操作:

SELECT * FROM single_table WHERE key1 = 'a' AND key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c';           

實際的例子:

mysql> show index from t_operater_record;
+-------------------+------------+---------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table             | Non_unique | Key_name                  | Seq_in_index | Column_name  | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------------------+------------+---------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| t_operater_record |          0 | PRIMARY                   |            1 | id           | A         |     1990233 |     NULL | NULL   |      | BTREE      |         |               |
| t_operater_record |          1 | i_updated_time            |            1 | updated_time | A         |     1037127 |     NULL | NULL   |      | BTREE      |         |               |
| t_operater_record |          1 | i_oper                    |            1 | operationer  | A         |      683749 |     NULL | NULL   |      | BTREE      |         |               |
| t_operater_record |          1 | i_num_status_created_time |            1 | num          | A         |       10106 |     NULL | NULL   |      | BTREE      |         |               |
| t_operater_record |          1 | i_num_status_created_time |            2 | status       | A         |      627083 |     NULL | NULL   |      | BTREE      |         |               |
| t_operater_record |          1 | i_num_status_created_time |            3 | created_time | A         |      998627 |     NULL | NULL   |      | BTREE      |         |               |
+-------------------+------------+---------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
6 rows in set (0.01 sec)

mysql> explain select * from t_operater_record where updated_time='2004-05-19 07:42:37' and num=7.34 and status=11 and created_time='2004-05-24 20:36:50';;
+----+-------------+-------------------+------------+-------------+------------------------------------------+------------------------------------------+---------+------+------+----------+------------------------------------------------------------------------+
| id | select_type | table             | partitions | type        | possible_keys                            | key                                      | key_len | ref  | rows | filtered | Extra                                                                  |
+----+-------------+-------------------+------------+-------------+------------------------------------------+------------------------------------------+---------+------+------+----------+------------------------------------------------------------------------+
|  1 | SIMPLE      | t_operater_record | NULL       | index_merge | i_updated_time,i_num_status_created_time | i_num_status_created_time,i_updated_time | 11,5    | NULL |    1 |   100.00 | Using intersect(i_num_status_created_time,i_updated_time); Using where |
+----+-------------+-------------------+------------+-------------+------------------------------------------+------------------------------------------+---------+------+------+----------+------------------------------------------------------------------------+           

而下邊這兩個查詢就不能進行Intersection索引合并:

SELECT * FROM single_table WHERE key1 > 'a' AND key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c';
SELECT * FROM single_table WHERE key1 = 'a' AND key_part1 = 'a';



mysql> explain select * from t_operater_record where updated_time='2004-05-19 07:42:37' and num=7.34 and status=11;
+----+-------------+-------------------+------------+------+------------------------------------------+----------------+---------+-------+------+----------+-------------+
| id | select_type | table             | partitions | type | possible_keys                            | key            | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------------------+------------+------+------------------------------------------+----------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | t_operater_record | NULL       | ref  | i_updated_time,i_num_status_created_time | i_updated_time | 5       | const |    3 |     1.67 | Using where |
+----+-------------+-------------------+------------+------+------------------------------------------+----------------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)           

情況二:

主鍵列可以是範圍比對。比方說下邊這個查詢可能用到主鍵和idx_key1進行Intersection索引合并的操作:

SELECT * FROM single_table WHERE id > 100 AND key1 = 'a';


mysql> explain select * from t_operater_record where updated_time='2004-05-19 07:42:37' and id>300000;
+----+-------------+-------------------+------------+-------------+------------------------+------------------------+---------+------+------+----------+------------------------------------------------------+
| id | select_type | table             | partitions | type        | possible_keys          | key                    | key_len | ref  | rows | filtered | Extra                                                |
+----+-------------+-------------------+------------+-------------+------------------------+------------------------+---------+------+------+----------+------------------------------------------------------+
|  1 | SIMPLE      | t_operater_record | NULL       | index_merge | PRIMARY,i_updated_time | i_updated_time,PRIMARY | 13,8    | NULL |    1 |   100.00 | Using intersect(i_updated_time,PRIMARY); Using where |
+----+-------------+-------------------+------------+-------------+------------------------+------------------------+---------+------+------+----------+------------------------------------------------------+
1 row in set, 1 warning (0.02 sec)           

為什麼會這樣呢?突然冒出這麼兩個規定讓人一臉懵逼。其實對于InnoDB的二級索引來說,記錄先是按照索引列進行排序,如果是一個聯合索引,那麼會按照各個列依次排序。而二級索引的記錄是由索引列 + 主鍵構成的,二級索引列的值相同的記錄可能會有好多條,這些索引列的值相同的記錄又是按照主鍵的值進行排序的。是以重點來了,之是以在二級索引列都是等值比對的情況下才可能使用Intersection索引合并,是因為隻有在這種情況下根據二級索引查詢出的結果集是按照主鍵值排序的。

而Intersection索引合并會把從多個二級索引中查詢出的主鍵值求交集,如果從各個二級索引中查詢的到的結果集本身就是已經按照主鍵排好序的,那麼求交集的過程就很easy啦。

而對于上面的情況二,二級索引的記錄中都帶有主鍵值的,是以可以在從idx_key1中擷取到的主鍵值上直接運用條件id > 100過濾就行了,這樣多簡單。

當然,上邊說的情況一和情況二隻是發生Intersection索引合并的必要條件,不是充分條件。

7.2 union合并

SELECT * FROM single_table WHERE key1 = 'a' OR key3 = 'b'           

Intersection是交集的意思,這适用于使用不同索引的搜尋條件之間使用AND連接配接起來的情況;Union是并集的意思,适用于使用不同索引的搜尋條件之間使用OR連接配接起來的情況。與Intersection索引合并類似,MySQL在某些特定的情況下才可能會使用到Union索引合并:

二級索引列是等值比對的情況,對于聯合索引來說,在聯合索引中的每個列都必須等值比對,不能出現隻出現比對部分列的情況。

比方說下邊這個查詢可能用到idx_key1和idx_key_part這兩個二級索引進行Union索引合并的操作:

SELECT * FROM single_table WHERE key1 = 'a' OR ( key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c');


mysql> explain select * from t_operater_record where updated_time='2004-05-19 07:42:37' or (num=7.34 and status=11 and created_time='2004-05-24 20:36:50');
+----+-------------+-------------------+------------+-------------+------------------------------------------+------------------------------------------+---------+------+------+----------+--------------------------------------------------------------------+
| id | select_type | table             | partitions | type        | possible_keys                            | key                                      | key_len | ref  | rows | filtered | Extra                                                              |
+----+-------------+-------------------+------------+-------------+------------------------------------------+------------------------------------------+---------+------+------+----------+--------------------------------------------------------------------+
|  1 | SIMPLE      | t_operater_record | NULL       | index_merge | i_updated_time,i_num_status_created_time | i_updated_time,i_num_status_created_time | 5,11    | NULL |    6 |   100.00 | Using union(i_updated_time,i_num_status_created_time); Using where |
+----+-------------+-------------------+------------+-------------+------------------------------------------+------------------------------------------+---------+------+------+----------+--------------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)           

而下邊這兩個查詢就不能進行Union索引合并:

SELECT * FROM single_table WHERE key1 > 'a' OR (key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c');
SELECT * FROM single_table WHERE key1 = 'a' OR key_part1 = 'a';           

主鍵列可以是範圍比對:

mysql> explain select * from t_operater_record where updated_time='2004-05-19 07:42:37' or id<100000;
+----+-------------+-------------------+------------+-------------+------------------------+------------------------+---------+------+------+----------+--------------------------------------------------+
| id | select_type | table             | partitions | type        | possible_keys          | key                    | key_len | ref  | rows | filtered | Extra                                            |
+----+-------------+-------------------+------------+-------------+------------------------+------------------------+---------+------+------+----------+--------------------------------------------------+
|  1 | SIMPLE      | t_operater_record | NULL       | index_merge | PRIMARY,i_updated_time | i_updated_time,PRIMARY | 5,8     | NULL |    4 |   100.00 | Using union(i_updated_time,PRIMARY); Using where |
+----+-------------+-------------------+------------+-------------+------------------------+------------------------+---------+------+------+----------+--------------------------------------------------+
1 row in set, 1 warning (0.00 sec)           

情況三:

使用Intersection索引合并的搜尋條件。

這種情況其實也挺好了解,就是搜尋條件的某些部分使用Intersection索引合并的方式得到的主鍵集合和其他方式得到的主鍵集合取交集,比方說這個查詢:

SELECT * FROM single_table WHERE key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c' OR (key1 = 'a' AND key3 = 'b');           

優化器可能采用這樣的方式來執行這個查詢:

先按照搜尋條件key1 = 'a' AND key3 = 'b'從索引idx_key1和idx_key3中使用Intersection索引合并的方式得到一個主鍵集合。

再按照搜尋條件key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c'從聯合索引中得到另一個主鍵集合。

采用Union索引合并的方式把上述兩個主鍵集合取并集,然後進行回表操作,将結果傳回給使用者。

當然,查詢條件符合了這些情況也不一定就會采用Union索引合并,也得看優化器的心情。

7.3 sort-union

Union索引合并的使用條件太苛刻,必須保證各個二級索引列在進行等值比對的條件下才可能被用到,比方說下邊這個查詢就無法使用到Union索引合并:

SELECT * FROM single_table WHERE key1 < 'a' OR key3 > 'z'           

這是因為根據key1 < 'a'從idx_key1索引中擷取的二級索引記錄的主鍵值不是排好序的,根據key3 > 'z'從idx_key3索引中擷取的二級索引記錄的主鍵值也不是排好序的,但是key1 < 'a'和key3 > 'z'這兩個條件又特别讓我們動心,是以我們可以這樣:

先根據key1 < 'a'條件從idx_key1二級索引中擷取記錄,并按照記錄的主鍵值進行排序

再根據key3 > 'z'條件從idx_key3二級索引中擷取記錄,并按照記錄的主鍵值進行排序

因為上述的兩個二級索引主鍵值都是排好序的,剩下的操作和Union索引合并方式就一樣了。

我們把上述這種先按照二級索引記錄的主鍵值進行排序,之後按照Union索引合并方式執行的方式稱之為Sort-Union索引合并,很顯然,這種Sort-Union索引合并比單純的Union索引合并多了一步對二級索引記錄的主鍵值排序的過程。

小貼士: 為啥有Sort-Union索引合并,就沒有Sort-Intersection索引合并麼?是的,的确沒有Sort-Intersection索引合并這麼一說, Sort-Union的适用場景是單獨根據搜尋條件從某個二級索引中擷取的記錄數比較少,這樣即使對這些二級索引記錄按照主鍵值進行排序的成本也不會太高;而Intersection索引合并的适用場景是單獨根據搜尋條件從某個二級索引中擷取的記錄數太多,導緻回表開銷太大,合并後可以明顯降低回表開銷,但是如果加入Sort-Intersection後,就需要為大量的二級索引記錄按照主鍵值進行排序,這個成本可能比回表查詢都高了,是以也就沒有引入Sort-Intersection這個玩意兒。

索引合并注意事項:

1、使用聯合索引代替Intersection索引合并往往是最優做法:

SELECT * FROM single_table WHERE key1 = 'a' AND key3 = 'b';           

這個查詢之是以可能使用Intersection索引合并的方式執行,還不是因為idx_key1和idx_key3是兩個單獨的B+樹索引,你要是把這兩個列搞一個聯合索引,那直接使用這個聯合索引就把事情搞定了,何必用啥索引合并呢。