MySQL ICP(Index Condition Pushdown)特性
一、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)特性