http://blog.163.com/li_hx/blog/static/1839914132015782821512/
一 什么是“索引條件下推”
“索引條件下推”,稱為 Index Condition Pushdown (ICP),這是MySQL提供的用某一個索引對一個特定的表從表中獲取元組”,注意我們這里特意強調了“一個”,這是因為這樣的索引優化不是用於多表連接而是用於單表掃描,確切地說,是單表利用索引進行掃描以獲取數據的一種方式。
二 “索引條件下推”的目的
用ySQL官方手冊描述:
The goal of ICP is to reduce the number of full-record reads and thereby reduce IO operations. For InnoDB clustered indexes, the complete record is already read into the InnoDB buffer. Using ICP in this case does not reduce IO.
這句官方描述,一是說明減少完整記錄(一條完整元組)讀取的個數;二是說明對於InnoDB聚集索引無效,只能是對SECOND INDEX這樣的非聚集索引有效。
三 原理
先看實例:
mysql> set optimizer_switch='index_condition_pushdown=off'; //關閉ICP
Query OK, 0 rows affected (0.00 sec)
mysql> EXPLAIN SELECT * FROM t4 WHERE 1=t4.a4 AND t4.name like 'char%';
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t4 | NULL | range | a4_i | a4_i | 28 | NULL | 1 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> set optimizer_switch='index_condition_pushdown=on'; //打開ICP,則Extra列中顯示“Using index condition”
Query OK, 0 rows affected (0.00 sec)
mysql> EXPLAIN SELECT * FROM t4 WHERE 1=t4.a4 AND t4.name like 'char%';
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | t4 | NULL | range | a4_i | a4_i | 28 | NULL | 1 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
如果打開ICP,則執行計划的Extra列會顯示“Using index condition”,這表明在。
借用網上的2張圖加以改造,並配以解釋,來說明原理,更清晰地說明問題。
圖一:不使用ICP技術(過程使用數字符號標示,如①②③等)

過程解釋:
①:MySQL Server發出讀取數據的命令,這是在執行器中執行如下代碼段,通過函數指針和handle接口調用存儲引擎的索引讀或全表表讀。此處進行的是索引讀。
if (in_first_read)
{
in_first_read= false;
error= (*qep_tab->read_first_record)(qep_tab); //設定合適的讀取函數,如設定索引讀函數/全表掃描函數
}
else
error= info->read_record(info);
②、③:進入存儲引擎,讀取索引樹,在索引樹上查找,把滿足條件的(經過查找,紅色的滿足)從表記錄中讀出(步驟④,通常有IO),從存儲引擎返回⑤標識的結果。此處,不僅要在索引行進行索引讀取(通常是內存中,速度快。步驟③),還要進行進行步驟④,通常有IO。
⑥:從存儲引擎返回查找到的多條元組給MySQL Server,MySQL Server在⑦得到較多的元組。
⑦--⑧:⑦到⑧依據WHERE子句條件進行過濾,得到滿足條件的元組。注意在MySQL Server層得到較多元組,然后才過濾,最終得到的是少量的、符合條件的元組。
圖二:使用ICP技術(過程使用數字符號標示,如①②③等)

過程解釋:
⑥:從存儲引擎返回查找到的少量元組給MySQL Server,MySQL Server在⑦得到少量的元組。因此比較圖一無ICP的方式,返回給MySQL Server層的即是少量的、符合條件的元組。
另外,圖中的部件層次關系,不再進行解釋。
四 實現細節
1 ICP只能用於輔助索引,不能用於聚集索引。
2 ICP只用於單表,不是多表連接是的連接條件部分(如開篇強調)
如果表訪問的類型為:
3 EQ_REF/REF_OR_NULL/REF/SYSTEM/CONST: 可以使用ICP
4 range:如果不是“index tree only(只讀索引)”,則有機會使用ICP
5 ALL/FT/INDEX_MERGE/INDEX_SCAN: 不可以使用ICP
五 上樓
1 條件下推,一直是SQL優化的基本規則。所以,條件下推技術是常規技術。數據庫的優化器幾乎不會不實現條件下推優化。
2 技術層面,MySQL存在MySQL Server層和儲存層,使得條件下推顯得“有些割裂”。
3 非技術層面,MySQL之所以引入ICP,猜一猜或拍拍腦袋,原因你懂得。
六 從代碼的角度看
對於圖一的解釋,給出了讀數據的代碼片段,無論是關閉還是打開ICP, 從下面給出的函數調用關系可以看出,2幅圖對應的情況下,代碼路徑是一致的.
首條元組讀取調用關系(藍色標識和非首條元組不同之處):
JOIN::exec()->do_select()->sub_select()->join_init_read_record()->rr_quick()->
QUICK_RANGE_SELECT::get_next()->ha_innobase::multi_range_read_next()->
DsMrr_impl::dsmrr_next()->handler::multi_range_read_next()->
handler::read_range_first()->handler::ha_index_read_map()->
handler::index_read_map()->ha_innobase::index_read()
除首條元組讀取調用關系(藍色標識和首條元組不同之處)
JOIN::exec()->do_select()->sub_select()->join_init_read_record()->rr_quick()->
QUICK_RANGE_SELECT::get_next()->ha_innobase::multi_range_read_next()->
DsMrr_impl::dsmrr_next()->handler::multi_range_read_next()->
handler::read_range_next()->handler::ha_index_next()->
ha_innobase::index_next()->ha_innobase::general_fetch()