MySQL中Explain初識
Index
MySQL索引的基本操作
CREATE INDEX idx_price on OrderItems(item_price);
ALTER TABLE OrderItems DROP INDEX idx_order_num_price;
Explain
MySQL 提供了一個 EXPLAIN 命令, 它可以對 SELECT 語句進行分析, 並輸出 SELECT 執行的詳細資訊, 以供開發人員針對性優化。EXPLAIN 命令用法十分簡單, 在 SELECT 語句前加上 Explain 就可以了。
Explain各列的含義如下:
id: 每個 SELECT 都會自動分配一個唯一的識別符號 select_type: SELECT 查詢的型別 table: 查詢的是哪個表 partitions: 匹配的分割槽 type: 訪問型別 possible_keys: 此次查詢中可能選用的索引 key: 此次查詢中確切使用到的索引 ref: 哪個欄位或常數與 key 一起被使用 rows: 顯示此查詢一共掃描了多少行 這個是一個估計值 filtered: 表示此查詢條件所過濾的資料的百分比 extra: 額外的資訊
其中Type是重點關注的欄位,用以快速評價查詢語句效能,常見type效能從差到好如下:
All < Index < Range < Ref < const
Demo
以OrderItems表為例:
| OrderItems | CREATE TABLE `OrderItems` ( `order_num` int(11) NOT NULL, `order_item` int(11) NOT NULL, `prod_id` char(10) NOT NULL, `quantity` int(11) NOT NULL, `item_price` decimal(8,2) NOT NULL, PRIMARY KEY (`order_num`,`order_item`), KEY `FK_OrderItems_Products` (`prod_id`), KEY `idx_price` (`item_price`), KEY `idx_order_num_price` (`order_num`,`item_price`), constRAINT `FK_OrderItems_Orders` FOREIGN KEY (`order_num`) REFERENCES `Orders` (`order_num`), CONSTRAINT `FK_OrderItems_Products` FOREIGN KEY (`prod_id`) REFERENCES `Products` (`prod_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 | mysql> select * from OrderItems; +-----------+------------+---------+----------+------------+ | order_num | order_item | prod_id | quantity | item_price | +-----------+------------+---------+----------+------------+ | 20005 | 1 | BR01 | 100 | 5.49 | | 20005 | 2 | BR03 | 100 | 10.99 | | 20006 | 1 | BR01 | 20 | 5.99 | | 20006 | 2 | BR02 | 10 | 8.99 | | 20006 | 3 | BR03 | 10 | 11.99 | | 20007 | 1 | BR03 | 50 | 11.49 | | 20007 | 2 | BNBG01 | 100 | 2.99 | | 20007 | 3 | BNBG02 | 100 | 2.99 | | 20007 | 4 | BNBG03 | 100 | 2.99 | | 20007 | 5 | RGAN01 | 50 | 4.49 | | 20008 | 1 | RGAN01 | 5 | 4.99 | | 20008 | 2 | BR03 | 5 | 11.99 | | 20008 | 3 | BNBG01 | 10 | 3.49 | | 20008 | 4 | BNBG02 | 10 | 3.49 | | 20008 | 5 | BNBG03 | 10 | 3.49 | | 20009 | 1 | BNBG01 | 250 | 2.49 | | 20009 | 2 | BNBG02 | 250 | 2.49 | | 20009 | 3 | BNBG03 | 250 | 2.49 | +-----------+------------+---------+----------+------------+ 18 rows in set (0.03 sec)
舉例說明幾個Explain type的查詢:
all,直接全表查詢
mysql> explain select item_price from OrderItems; +----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+ | 1 | SIMPLE | OrderItems | NULL | ALL | NULL | NULL | NULL | NULL | 18 | 100.00 | NULL | +----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
index,利用索引
mysql> explain select order_num from OrderItems;
+----+-------------+------------+------------+-------+---------------+------------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+------------------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | OrderItems | NULL | index | NULL | FK_OrderItems_Products | 30 | NULL | 18 | 100.00 | Using index |
+----+-------------+------------+------------+-------+---------------+------------------------+---------+------+------+----------+-------------+
range,對索引進行範圍查詢,多見於between/in/<>等關鍵字
mysql> explain select order_num from OrderItems where order_num between 20003 and 20005;
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | OrderItems | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where; Using index |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
ref,使用普通索引
mysql> explain select order_num from OrderItems where order_num = 20005;
+----+-------------+------------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | OrderItems | NULL | ref | PRIMARY | PRIMARY | 4 | const | 2 | 100.00 | Using index |
+----+-------------+------------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
consts 單表中最多隻有一個匹配行(主鍵或者唯一索引),在優化階段即可讀取到數。
Optimization
通過分析Explain語句結果可以優化查詢效能。一般關注點有:一是type往好的方向優化,二是有order by時,儘量不要在extra中出現Using filesort。
type優化,如 阿里巴巴Java程式設計規範 中定義的那樣,一般不允許all和index的查詢,會極大影響效能。儘量優化至range以上。方法一般就是建索引,不要為了節省插入效能而去縮減必要的索引。
mysql> CREATE INDEX idx_price on OrderItems(item_price);
然後再執行關於item_price的查詢:
mysql> explain select order_num from OrderItems where item_price between 5 and 8;
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | OrderItems | NULL | range | idx_price | idx_price | 4 | NULL | 2 | 100.00 | Using where; Using index |
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)
看到type位置變為range,possible_keys和key的位置出現了我們新建的索引
去除Using filesort
執行下列查詢
mysql> explain select * from OrderItems where order_num = 20003 order by item_price;
+----+-------------+------------+------------+------+---------------+---------+---------+-------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+---------+---------+-------+------+----------+-----------------------------+
| 1 | SIMPLE | OrderItems | NULL | ref | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using where; Using filesort |
+----+-------------+------------+------------+------+---------------+---------+---------+-------+------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)
使用了fileSort。我們建立索引並再次執行查詢如下:
mysql> CREATE INDEX idx_order_num_price on OrderItems(order_num, item_price);
mysql> explain select * from OrderItems where order_num = 20003 order by item_price;
+----+-------------+------------+------------+------+-----------------------------+---------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+-----------------------------+---------------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | OrderItems | NULL | ref | PRIMARY,idx_order_num_price | idx_order_num_price | 4 | const | 1 | 100.00 | Using index condition |
+----+-------------+------------+------------+------+-----------------------------+---------------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.01 sec)
如果建立了多重索引A-B(A和B是column name),那麼查詢語句的where clause中僅使用了A也是可以利用該A-B索引的。事實上,只要查詢條件從左至右依次匹配某索引,都是可以利用的。
https://www.renrenfan.com.cn https://www.houdianzi.com
Problems
另外遇到兩個關於索引有序性使用的小坑。
查詢語句中的欄位匹配索引的前半部分,但如果它們是用於in/between,索引失效
比如在建立了order_num/item_price索引的情況下,還是會fileSort:
mysql> explain select order_num from OrderItems where order_num between 20003 and 20005 order by item_price;
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | OrderItems | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where; Using filesort |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------------+
單一Order時使用索引不分升降序,但如果對多欄位排序,則要求索引順序和查詢語句’一致’
mysql> explain select item_price from OrderItems ORDER BY order_num, item_price;
+----+-------------+------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | OrderItems | NULL | index | NULL | idx_order_num_price | 8 | NULL | 18 | 100.00 | Using index |
+----+-------------+------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select item_price from OrderItems ORDER BY order_num, item_price desc;
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | OrderItems | NULL | index | NULL | idx_price | 4 | NULL | 18 | 100.00 | Using index; Using filesort |
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select item_price from OrderItems ORDER BY order_num desc, item_price desc;
+----+-------------+------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | OrderItems | NULL | index | NULL | idx_order_num_price | 8 | NULL | 18 | 100.00 | Using index |
+----+-------------+------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
如果理解了MySQL索引的物理實現(B+ Tree),這些應該就比較容易理解了(TODO)。