1. 程式人生 > >MySQL ICP(Index Condition Pushdown)特性

MySQL ICP(Index Condition Pushdown)特性

nal 沒有 ray 定義 最終 存儲 null fis varchar

一、SQL的where條件提取規則

在ICP(Index Condition Pushdown,索引條件下推)特性之前,必須先搞明白根據何登成大神總結出一套放置於所有SQL語句而皆準的where查詢條件的提取規則:所有SQL的where條件,均可歸納為3大類:Index Key (First Key & Last Key),Index Filter,Table Filter。

接下來,簡單說一下這3大類分別是如何定義,以及如何提取的,詳情請看:SQL語句中where條件在數據庫中提取與應用淺析。

Index Key(Fist key & Last Key),Index Filter,Table Filter

Index First Key

只是用來定位索引的起始範圍,因此只在索引第一次Search Path(沿著索引B+樹的根節點一直遍歷,到索引正確的葉節點位置)時使用,一次判斷即可;

Index Last Key

用來定位索引的終止範圍,因此對於起始範圍之後讀到的每一條索引記錄,均需要判斷是否已經超過了Index Last Key的範圍,若超過,則當前查詢結束;

Index Filter

用於過濾索引查詢範圍中不滿足查詢條件的記錄,因此對於索引範圍中的每一條記錄,均需要與Index Filter進行對比,若不滿足Index Filter則直接丟棄,繼續讀取索引下一條記錄;

Table Filter

則是最後一道where條件的防線,用於過濾通過前面索引的層層考驗的記錄,此時的記錄已經滿足了Index First Key與Index Last Key構成的範圍,並且滿足Index Filter的條件,回表讀取了完整的記錄,判斷完整記錄是否滿足Table Filter中的查詢條件,同樣的,若不滿足,跳過當前記錄,繼續讀取索引的下一條記錄,若滿足,則返回記錄,此記錄滿足了where的所有條件,可以返回給前端用戶。

二、ICP特性介紹

Index Condition Pushdown (ICP)是MySQL 5.6版本中的新特性,是一種在存儲引擎層使用索引過濾數據的一種優化方式。

我對Using index condition的理解是,首先mysql server和storage engine是兩個組件,server負責sql的parse,執行; storage engine去真正的做數據/index的讀取/寫入。以前是這樣:server命令storage engine按index key把相應的數據從數據表讀出,傳給server,然後server來按where條件(index filter和table filter)做選擇。

而在MySQL 5.6加入ICP後,Index Filter與Table Filter分離,Index Filter下降到InnoDB的索引層面進行過濾,如果不符合條件則無須讀數據表,減少了回表與返回MySQL Server層的記錄交互開銷,節省了disk IO,提高了SQL的執行效率。

原理

a. 當關閉ICP時,index僅僅是data access的一種訪問方式,存儲引擎通過索引回表獲取的數據會傳遞到MySQL Server層進行where條件過濾,也就是做index filter和table filter。

b. 當打開ICP時,如果部分where條件能使用索引中的字段,MySQL Server會把這部分下推到引擎層,可以利用index filter的where條件在存儲引擎層進行數據過濾,而非將所有通過index access的結果傳遞到MySQL server層進行where過濾。

優化效果:ICP能減少引擎層訪問基表的次數和MySQL Server訪問存儲引擎的次數,減少io次數,提高查詢語句性能。

三、測試ICP優化SQL

本文選用MySQL官方文檔中提供的示例數據庫之一:employees。這個數據庫關系復雜度適中,且數據量較大。下圖是這個數據庫的E-R關系圖(引用自MySQL官方手冊):

技術分享圖片

MySQL官方文檔中關於此數據庫的頁面為:https://dev.mysql.com/doc/employee/en,裏面詳細介紹了此數據庫,並提供了下載地址和導入方法,如果有興趣導入此數據庫到自己的MySQL可以參考文中內容。

可以選擇下載測試數據:https://github.com/datacharmer/test_db

關閉緩存(最好關閉後重啟MySQL)

mysql> set global query_cache_size=0;
mysql> set global query_cache_type=OFF;

導入employees庫,需要自己手動創建一個聯合索引。

mysql> alter table employees add index first_last(first_name,last_name);

其表結構如下:

mysql> show create table employees\G
*************************** 1. row ***************************
       Table: employees
Create Table: CREATE TABLE `employees` (
  `emp_no` int(11) NOT NULL,
  `birth_date` date NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name` varchar(16) NOT NULL,
  `gender` enum(M,F) NOT NULL,
  `hire_date` date NOT NULL,
  PRIMARY KEY (`emp_no`),
  KEY `first_last` (`first_name`,`last_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

當開啟ICP時(默認開啟)

mysql> SET profiling = 1;
Query OK, 0 rows affected, 1 warning (0.00 sec)
 
mysql> select SQL_NO_CACHE * from employees where first_name=Anneke and last_name like %Preusig ;
+--------+------------+------------+-----------+--------+------------+
| emp_no | birth_date | first_name | last_name | gender | hire_date |
+--------+------------+------------+-----------+--------+------------+
| 10006  | 1953-04-20 | Anneke     | Preusig   | F      | 1989-06-02 |
+--------+------------+------------+-----------+--------+------------+
1 row in set (0.00 sec)

此時情況下根據MySQL的最左前綴原則,irst_name 可以使用索引,last_name采用了like 模糊查詢,不能使用索引。

當關閉ICP時

mysql> set optimizer_switch=index_condition_pushdown=off;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select SQL_NO_CACHE * from employees where first_name=Anneke and last_name like %Preusig ;
+--------+------------+------------+-----------+--------+------------+
| emp_no | birth_date | first_name | last_name | gender | hire_date |
+--------+------------+------------+-----------+--------+------------+
| 10006  | 1953-04-20 | Anneke     | Preusig   | F      | 1989-06-02 |
+--------+------------+------------+-----------+--------+------------+
1 row in set (0.00 sec)
 
mysql> SET profiling = 0;
Query OK, 0 rows affected, 1 warning (0.00 sec)
 
mysql> show profiles;
+----------+------------+---------------------------------------------------------------------------------+
| Query_ID | Duration   | Query                                                                           |
+----------+------------+---------------------------------------------------------------------------------+
|        1 | 0.00108900 | select * from employees where first_name=Anneke and last_name like %Preusig |
|        2 | 0.00025375 | set optimizer_switch=index_condition_pushdown=off                             |
|        3 | 0.00231650 | select * from employees where first_name=Anneke and last_name like %Preusig |
+----------+------------+---------------------------------------------------------------------------------+
5 rows in set, 1 warning (0.00 sec)

當開啟ICP時,查詢在sending data環節時間消耗是 0.00108900s

當關閉ICP時,查詢在sending data環節時間消耗是 0.00231650s

從上面的profile可以看出ICP開啟時整個sql 執行時間是未開啟的2/3,sending data 環節的時間消耗前者僅是後者的1/4。

ICP 開啟時的執行計劃 含有 Using index condition 標示 ,表示優化器使用了ICP對數據訪問進行優化。ICP關閉時的執行計劃顯示use where。
mysql> set optimizer_switch=index_condition_pushdown=on;
Query OK, 0 rows affected (0.00 sec)
 
mysql> explain select * from employees where first_name=Anneke and last_name like %nta ;
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
| id | select_type | table     | type | possible_keys | key        | key_len | ref   | rows | Extra                 |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | employees | ref  | first_last    | first_last | 58      | const |  224 | Using index condition |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)
 
mysql> explain select * from employees where first_name=Anneke ;
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
| id | select_type | table     | type | possible_keys | key        | key_len | ref   | rows | Extra                 |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | employees | ref  | first_last    | first_last | 58      | const |  224 | Using index condition |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)
 
mysql> set optimizer_switch=index_condition_pushdown=off;
Query OK, 0 rows affected (0.00 sec)
 
mysql> explain select * from employees where first_name=Anneke and last_name like %nta ;
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-------------+
| id | select_type | table     | type | possible_keys | key        | key_len | ref   | rows | Extra       |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-------------+
|  1 | SIMPLE      | employees | ref  | first_last    | first_last | 58      | const |  224 | Using where |
+----+-------------+-----------+------+---------------+------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

四、案例分析

以上面的查詢為例,關閉ICP時,存儲引擎通前綴index first_name(視為index key)訪問表中數據,並在MySQL server層根據where條件last_name like ‘%nta’(視為index filter)進行過濾。

開啟ICP時,MySQL server把index filter(last_name like ‘%nta’)推到存儲引擎層,在存儲引擎內部通過與where條件last_name like ‘%nta’的對比,直接過濾掉不符合條件的數據,然後返回最終數據給MySQL server層。該過程減少了回表操作,只訪問符合條件的1條記錄並返回給MySQL Server ,有效的減少了io訪問和各層之間的交互。

ICP關閉時 ,僅僅使用索引作為訪問數據的方式。

技術分享圖片

ICP 開啟時 ,MySQL將在存儲引擎層 利用索引過濾數據,減少不必要的回表。

技術分享圖片

註意虛線的using where表示如果where條件中含有沒有被索引的字段,則還是要經過MySQL Server 層過濾。

五、ICP的使用限制

1. 當sql需要全表訪問時,ICP的優化策略可用於range, ref, eq_ref, ref_or_null類型的訪問數據方法 。

2. 支持InnoDB和MyISAM表。

3. ICP只能用於二級索引,不能用於主索引。

4. 並非全部where條件都可以用ICP篩選,如果where條件的字段不在索引列中,還是要讀取整表的記錄到server端做where過濾。

5. ICP的加速效果取決於在存儲引擎內通過ICP篩選掉的數據的比例。

6. MySQL 5.6版本的不支持分表的ICP功能,5.7版本的開始支持。

7. 當sql使用覆蓋索引時,不支持ICP優化方法。

原文鏈接:http://blog.itpub.net/22664653/viewspace-1210844/

MySQL ICP(Index Condition Pushdown)特性