第26期:索引設計(索引下推)

索引下推(INDEX CONDITION PUSHDOWN,簡稱 ICP)是 MySQL 5.6 發佈後針對掃描二級索引的一項優化改進。總的來講是經過把索引過濾條件下推到存儲引擎,來減小 MySQL 存儲引擎訪問基表的次數以及 MySQL 服務層訪問存儲引擎的次數。ICP 適用於 MYISAM 和 INNODB,本篇的內容只基於 INNODB。mysql

MySQL ICP 裏涉及到的知識點以下:

1.MySQL 服務層:也就是 SERVER 層,用來解析 SQL 的語法、語義、生成查詢計劃、接管從 MySQL 存儲引擎層上推的數據進行二次過濾等等。sql

2.MySQL 存儲引擎層:按照 MySQL 服務層下發的請求,經過索引或者全表掃描等方式把數據上傳到 MySQL 服務層。函數

3.MySQL 索引掃描:根據指定索引過濾條件(好比 where id = 1) ,遍歷索引找到索引鍵對應的主鍵值後回表過濾剩餘過濾條件。性能

4.MySQL 索引過濾:經過索引掃描而且基於索引進行二次條件過濾後再回表。優化

ICP 就是把以上索引掃描和索引過濾合併在一塊兒處理,過濾後的記錄數據下推到存儲引擎後的一種索引優化策略。這樣作的優勢以下:spa

1.減小了回表的操做次數。code

2.減小了上傳到 MySQL SERVER 層的數據。索引

ICP 默認開啓,可經過優化器開關參數關閉 ICP:optimizer_switch='index_condition_pushdown=off' 或者是在 SQL 層面經過 HINT 來關閉。rem

接下來,詳細看下不適用 ICP、使用 ICP 的詳細示例來理清 ICP 的概念。it

在不使用 ICP 索引掃描的過程:

MySQL 存儲引擎層只把知足索引鍵值對應的整行表記錄一條一條取出,而且上傳給 MySQL 服務層。

MySQL 服務層對接收到的數據,使用 SQL 語句後面的 where 條件過濾,直處處理完最後一行記錄,再一塊兒返回給客戶端。

假設 SQL 語句爲:

(localhost:mysqld.sock)|(ytt)>select * from t1 where r1 = 1 and r2 like '%dog%' and r4 = 5\G
*************************** 1. row ***************************
id: 28965
f1: 81
f2: 89
f3: 100
f4: 35
r1: 1
r2: 12844bda dog 11ea a051 08002753f58d
r3: 17
r4: 5
1 row in set (0.00 sec)

關閉 ICP 的處理流程大概如圖 1:

使用 ICP 掃描的過程:

MySQL 存儲引擎層,先根據過濾條件中包含的索引鍵肯定索引記區間,再在這個區間的記錄上使用包含索引鍵的其餘過濾條件進行過濾,以後規避掉不知足的索引記錄,只根據知足條件的索引記錄回表取回數據上傳到 MySQL 服務層。

MySQL 服務層對接收到的數據,使用 where 子句中不包含索引列的過濾條件作最後的過濾,而後返回數據給客戶端。

以下圖所示:

上面兩張圖很明顯的對比出開啓 ICP 比不開啓 ICP 的效率。返回數據這一塊虛線表示規避掉的記錄,開啓 ICP 很明顯減小了上傳到 MySQL 存儲引擎層、MySQL 服務層的記錄條數,節省了 IO。

查看語句是否用了 ICP,只須要對語句進行 EXPLAIN,在 EXTRA 信息裏能夠看到 ICP 相關信息。

如下爲分別爲關閉 ICP 與開啓 ICP 的 EXPLAIN 結果:

(localhost:mysqld.sock)|(ytt)>explain select /*+ no_icp (t1)  */ * from t1 where r1 = 1 and r2 like '%dog%' and r4 = 5\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ref
possible_keys: idx_r4,idx_u1
          key: idx_u1
      key_len: 5
          ref: const
         rows: 325
     filtered: 0.12
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

(localhost:mysqld.sock)|(ytt)>explain select * from t1 where r1 = 1 and r2 like '%dog%' and r4 = 5\G  *************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ref
possible_keys: idx_r4,idx_u1
          key: idx_u1
      key_len: 5
          ref: const
         rows: 325
     filtered: 0.12
        Extra: Using index condition; Using where
1 row in set, 1 warning (0.00 sec)

其中 extra 裏顯示 「Using index condition」 就表明用了 ICP。不過這個信息有點過於簡單了,除了 EXTRA 列結果顯示不一樣外,其餘的列結果都同樣,無法從執行計劃結果判斷 ICP 的優略。

能夠經過如下幾種方法來查看 ICP 帶來的直觀性能提高。

1.show status like '%handler%'

show status 語句能夠查看對存儲引擎的相關指標監控結果。從如下結果能夠看出:指標 Handler_read_next(表示 MySQL 存儲引擎按照索引鍵順序讀取下一行記錄的請求數,也就是說這個值表示按照索引鍵值來訪問基表的請求數)在沒有開啓 ICP 時,值爲 325,也就是說對基表讀取請求 325 次;而開啓 ICP 後,這個值僅有 14 次。因此開啓 ICP 效率提高很明顯。

(localhost:mysqld.sock)|(ytt)>flush status;
Query OK, 0 rows affected (0.01 sec)

(localhost:mysqld.sock)|(ytt)> select /*+ no_icp (t1) */ * from t1 where r1 = 1 and r2 like '%dog%' and r4 = 5\G
*************************** 1. row ***************************
id: 28965
f1: 81
f2: 89
f3: 100
f4: 35
r1: 1
r2: 12844bda dog 11ea a051 08002753f58d
r3: 17
r4: 5
1 row in set (0.00 sec)


(localhost:mysqld.sock)|(ytt)>show status like '%handler%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_external_lock      | 2     |
| Handler_mrr_init           | 0     |
| Handler_prepare            | 0     |
| Handler_read_first         | 0     |
| Handler_read_key           | 1     |
| Handler_read_last          | 0     |
| Handler_read_next          | 325   |
| Handler_read_prev          | 0     |
| Handler_read_rnd           | 0     |
| Handler_read_rnd_next      | 0     |
| Handler_rollback           | 0     |
| Handler_savepoint          | 0     |
| Handler_savepoint_rollback | 0     |
| Handler_update             | 0     |
| Handler_write              | 0     |
+----------------------------+-------+
18 rows in set (0.00 sec)

(localhost:mysqld.sock)|(ytt)>flush status;
Query OK, 0 rows affected (0.01 sec)


(localhost:mysqld.sock)|(ytt)>select * from t1 where r1 = 1 and r2 like '%dog%' and r4 = 5\G
*************************** 1. row ***************************
id: 28965
f1: 81
f2: 89
f3: 100
f4: 35
r1: 1
r2: 12844bda dog 11ea a051 08002753f58d
r3: 17
r4: 5
1 row in set (0.00 sec)


(localhost:mysqld.sock)|(ytt)>show status like '%handler%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_external_lock      | 2     |
| Handler_mrr_init           | 0     |
| Handler_prepare            | 0     |
| Handler_read_first         | 0     |
| Handler_read_key           | 1     |
| Handler_read_last          | 0     |
| Handler_read_next          | 14    |
| Handler_read_prev          | 0     |
| Handler_read_rnd           | 0     |
| Handler_read_rnd_next      | 0     |
| Handler_rollback           | 0     |
| Handler_savepoint          | 0     |
| Handler_savepoint_rollback | 0     |
| Handler_update             | 0     |
| Handler_write              | 0     |
+----------------------------+-------+
18 rows in set (0.00 sec)

(localhost:mysqld.sock)|(ytt)>

2.開啓 profiles

查看 profile 結果的整體時間,關閉 ICP 爲:0.00101900,開啓ICP爲:0.00100325。時間上 ICP 佔優點。

(localhost:mysqld.sock)|(ytt)>set profiling=1;
Query OK, 0 rows affected, 1 warning (0.00 sec)

(localhost:mysqld.sock)|(ytt)>show profiles;
Empty set, 1 warning (0.00 sec)

(localhost:mysqld.sock)|(ytt)> select /*+ no_icp (t1) */ * from t1 where r1 = 1 and r2 like '%dog%' and r4 = 5\G
*************************** 1. row ***************************
id: 28965
f1: 81
f2: 89
f3: 100
f4: 35
r1: 1
r2: 12844bda dog 11ea a051 08002753f58d
r3: 17
r4: 5
1 row in set (0.00 sec)

(localhost:mysqld.sock)|(ytt)> select  * from t1 where r1 = 1 and r2 like '%dog%' and r4 = 5\G        *************************** 1. row ***************************
id: 28965
f1: 81
f2: 89
f3: 100
f4: 35
r1: 1
r2: 12844bda dog 11ea a051 08002753f58d
r3: 17
r4: 5
1 row in set (0.00 sec)


(localhost:mysqld.sock)|(ytt)>show profiles\G
*************************** 1. row ***************************
Query_ID: 1
Duration: 0.00101900
Query: select /*+ no_icp (t1) */ * from t1 where r1 = 1 and r2 like '%dog%' and r4 = 5
*************************** 2. row ***************************
Query_ID: 2
Duration: 0.00100325
Query: select  * from t1 where r1 = 1 and r2 like '%dog%' and r4 = 5
2 rows in set, 1 warning (0.00 sec)

任何須要下推到底層存儲層的操做通常都有諸多限制,MySQL ICP 也不例外,ICP 限制以下:

1.ICP 僅用於須要訪問基表全部記錄時使用,適用的訪問方法爲:range、ref、eq_ref、ref_or_null。我上面舉的例子便是 ref 類型,ICP 尤爲是對聯合索引的部分列模糊查找很是有效。

2.ICP 一樣適用於分區表。

3.ICP 的目標是減小全行記錄讀取,從而減小 I/O 操做,僅用於二級索引。主鍵索引自己便是表數據,不存在下推操做。

4.ICP 不支持基於虛擬列上創建的索引,好比函數索引。

5.ICP 不支持引用子查詢的條件。


關於 MySQL 的技術內容,大家還有什麼想知道的嗎?趕忙留言告訴小編吧!

相關文章
相關標籤/搜索