MySQL索引與Index Condition Pushdown

大約在兩年前,我寫了一篇關於MySQL索引的文章。最近有同窗在文章的評論中對文章的內容提出質疑,質疑主要集中在聯合索引的使用方式上。在那篇文章中,我說明聯合索引是將各個索引字段作字符串鏈接後做爲key,使用時將總體作前綴匹配。html

而這名同窗在這個頁面找到了以下一句話:index condition pushdown is usually useful with multi-column indexes: the first component(s) is what index access is done for, the subsequent have columns that we read and check conditions on。從而認爲聯合索引的使用方式與文中不符。mysql

實際上,這個頁面所講述的是在MariaDB 5.3.3(MySQL是在5.6)開始引入的一種叫作Index Condition Pushdown(如下簡稱ICP)的查詢優化方式。因爲自己不是一個層面的東西,前文中說的是Index Access,而這裏是Query Optimization,因此並不構成對前文正確性的影響。在寫前文時,MySQL尚未ICP,因此文中沒有涉及相關內容,但考慮到新版本的MariaDB或MySQL中ICP的啓用確實影響了一些查詢行爲的外在表現。因此決定寫這篇文章詳細講述一下ICP的原理以及對索引使用方式的優化。sql

實驗

先從一個簡單的實驗開始直觀認識ICP的做用。數據庫

安裝數據庫

首先須要安裝一個支持ICP的MariaDB或MySQL數據庫。我使用的是MariaDB 5.5.34,若是是使用MySQL則須要5.6版本以上。bash

Mac環境下能夠經過brew安裝:性能

brew install mairadb

其它環境下的安裝請參考MariaDB官網關於下載安裝的文檔優化

導入示例數據

與前文同樣,咱們使用Employees Sample Database,做爲示例數據庫。完整示例數據庫的下載地址爲:https://launchpad.net/test-db/employees-db-1/1.0.6/+download/employees_db-full-1.0.6.tar.bz2spa

將下載的壓縮包解壓後,會看到一系列的文件,其中employees.sql就是導入數據的命令文件。執行.net

mysql -h[host] -u[user] -p < employees.sql

就能夠完成建庫、建表和load數據等一系列操做。此時數據庫中會多一個叫作employees的數據庫。庫中的表以下:code

MariaDB [employees]> SHOW TABLES;
+---------------------+
| Tables_in_employees |
+---------------------+
| departments |
| dept_emp |
| dept_manager |
| employees |
| salaries |
| titles |
+---------------------+
6 rows in set (0.00 sec)

咱們將使用employees表作實驗。

創建聯合索引

employees表包含僱員的基本信息,表結構以下:

MariaDB [employees]> DESC employees.employees;
+------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------+------+-----+---------+-------+
| emp_no | int(11) | NO | PRI | NULL | |
| birth_date | date | NO | | NULL | |
| first_name | varchar(14) | NO | | NULL | |
| last_name | varchar(16) | NO | | NULL | |
| gender | enum('M','F') | NO | | NULL | |
| hire_date | date | NO | | NULL | |
+------------+---------------+------+-----+---------+-------+
6 rows in set (0.01 sec)

這個表默認只有一個主索引,由於ICP只能做用於二級索引,因此咱們創建一個二級索引:

ALTER TABLE employees.employees ADD INDEX first_name_last_name (first_name, last_name);

這樣就創建了一個first_name和last_name的聯合索引。

查詢

爲了明確看到查詢性能,咱們啓用profiling並關閉query cache:

SET profiling = 1;
SET query_cache_type = 0;
SET GLOBAL query_cache_size = 0;

而後咱們看下面這個查詢:

MariaDB [employees]> SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man';
+--------+------------+------------+-----------+--------+------------+
| emp_no | birth_date | first_name | last_name | gender | hire_date |
+--------+------------+------------+-----------+--------+------------+
| 254642 | 1959-01-17 | Mary | Botman | M | 1989-11-24 |
| 471495 | 1960-09-24 | Mary | Dymetman | M | 1988-06-09 |
| 211941 | 1962-08-11 | Mary | Hofman | M | 1993-12-30 |
| 217707 | 1962-09-05 | Mary | Lichtman | F | 1987-11-20 |
| 486361 | 1957-10-15 | Mary | Oberman | M | 1988-09-06 |
| 457469 | 1959-07-15 | Mary | Weedman | M | 1996-11-21 |
+--------+------------+------------+-----------+--------+------------+

根據MySQL索引的前綴匹配原則,二者對索引的使用是一致的,即只有first_name採用索引,last_name因爲使用了模糊前綴,無法使用索引進行匹配。我將查詢聯繫執行三次,結果以下:

+----------+------------+---------------------------------------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+---------------------------------------------------------------------------+
| 38 | 0.00084400 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
| 39 | 0.00071800 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
| 40 | 0.00089600 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
+----------+------------+---------------------------------------------------------------------------+

而後咱們關閉ICP:

SET optimizer_switch='index_condition_pushdown=off';

在運行三次相同的查詢,結果以下:

+----------+------------+---------------------------------------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+---------------------------------------------------------------------------+
| 42 | 0.00264400 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
| 43 | 0.01418900 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
| 44 | 0.00234200 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
+----------+------------+---------------------------------------------------------------------------+

有意思的事情發生了,關閉ICP後,一樣的查詢,耗時是以前的三倍以上。下面咱們用explain看看二者有什麼區別:

MariaDB [employees]> EXPLAIN SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man';
+------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-----------------------+
| 1 | SIMPLE | employees | ref | first_name_last_name | first_name_last_name | 44 | const | 224 | Using index condition |
+------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)
 
MariaDB [employees]> EXPLAIN SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man';
+------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-------------+
| 1 | SIMPLE | employees | ref | first_name_last_name | first_name_last_name | 44 | const | 224 | Using where |
+------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

前者是開啓ICP,後者是關閉ICP。能夠看到區別在於Extra,開啓ICP時,用的是Using index condition;關閉ICP時,是Using where。

其中Using index condition就是ICP提升查詢性能的關鍵。下一節說明ICP提升查詢性能的原理。

原理

ICP的原理簡單說來就是將能夠利用索引篩選的where條件在存儲引擎一側進行篩選,而不是將全部index access的結果取出放在server端進行where篩選。

以上面的查詢爲例,在沒有ICP時,首先經過索引前綴從存儲引擎中讀出224條first_name爲Mary的記錄,而後在server段用where篩選last_name的like條件;而啓用ICP後,因爲last_name的like篩選能夠經過索引字段進行,那麼存儲引擎內部經過索引與where條件的對比來篩選掉不符合where條件的記錄,這個過程不須要讀出整條記錄,同時只返回給server篩選後的6條記錄,所以提升了查詢性能。

下面經過圖兩種查詢的原理詳細解釋。

關閉ICP

在不支持ICP的系統下,索引僅僅做爲data access使用。

開啓ICP

在ICP優化開啓時,在存儲引擎端首先用索引過濾能夠過濾的where條件,而後再用索引作data access,被index condition過濾掉的數據沒必要讀取,也不會返回server端。

注意事項

有幾個關於ICP的事情要注意:

  • ICP只能用於二級索引,不能用於主索引。
  • 也不是所有where條件均可以用ICP篩選,若是某where條件的字段不在索引中,固然仍是要讀取整條記錄作篩選,在這種狀況下,仍然要到server端作where篩選。
  • ICP的加速效果取決於在存儲引擎內經過ICP篩選掉的數據的比例。

參考

[1] https://mariadb.com/kb/en/index-condition-pushdown/

[2] http://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html

相關文章
相關標籤/搜索