MySQL InnoDB的二級索引(Secondary Index)會自動補齊主鍵,將主鍵列追加到二級索引列後面。詳細一點來講,InnoDB的二級索引(Secondary Index)除了存儲索引列key值,還存儲着主鍵的值(而不是指向主鍵的指針)。爲何這樣作呢?由於InnoDB是以彙集索引方式組織數據的存儲,即主鍵值相鄰的數據行緊湊的存儲在一塊兒(索引組織表)。當數據行移動或者發生頁分裂的時候,能夠減小大量的二級索引維護工做。InnoDB移動行時,無需更新二級索引。咱們以官方文檔的例子來測試:html
CREATE TABLE t1 (
i1 INT NOT NULL DEFAULT 0,
i2 INT NOT NULL DEFAULT 0,
d DATE DEFAULT NULL,
PRIMARY KEY (i1, i2),
INDEX k_d (d)
) ENGINE = InnoDB;
如上所示,這個t1表包含主鍵和二級索引k_d,二級索引k_d(d)的元組在InnoDB內部實際被擴展成(d,i1,i2),即包含主鍵值。所以在設計主鍵的時候,常見的一條設計原則是要求主鍵字段儘可能簡短,以免二級索引過大(由於二級索引會自動補齊主鍵字段)。mysql
優化器會考慮擴展二級索引的主鍵列,肯定何時使用以及如何使用該索引。 這樣能夠產生更高效的執行計劃和達到更好的性能。有很多博客介紹索引擴展是從MySQL5.6.9開始引入的。不過我的尚未在官方文檔看到相關資料。sql
優化器能夠用擴展的二級索引來進行ref,range,index_merge等類型索引訪問(index access),鬆散的索引掃描(index sacns),鏈接和排序優化,以及min()/max()優化。緩存
咱們先來插入測試數據(腳原本自官方文檔):oracle
INSERT INTO t1 VALUES
(1, 1, '1998-01-01'), (1, 2, '1999-01-01'),
(1, 3, '2000-01-01'), (1, 4, '2001-01-01'),
(1, 5, '2002-01-01'), (2, 1, '1998-01-01'),
(2, 2, '1999-01-01'), (2, 3, '2000-01-01'),
(2, 4, '2001-01-01'), (2, 5, '2002-01-01'),
(3, 1, '1998-01-01'), (3, 2, '1999-01-01'),
(3, 3, '2000-01-01'), (3, 4, '2001-01-01'),
(3, 5, '2002-01-01'), (4, 1, '1998-01-01'),
(4, 2, '1999-01-01'), (4, 3, '2000-01-01'),
(4, 4, '2001-01-01'), (4, 5, '2002-01-01'),
(5, 1, '1998-01-01'), (5, 2, '1999-01-01'),
(5, 3, '2000-01-01'), (5, 4, '2001-01-01'),
(5, 5, '2002-01-01');
#默認狀況下,索引擴展(use_index_extensions)選項是開啓的。能夠在當前會話經過修改優化器開關optimizer_switch開啓、關閉此選項。app
mysql> show variables like '%optimizer_switch%';
mysql> SET optimizer_switch = 'use_index_extensions=off';
Query OK, 0 rows affected (0.00 sec)
mysql> EXPLAIN
-> SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01';
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
| 1 | SIMPLE | t1 | ref | PRIMARY,k_d | k_d | 4 | const | 5 | Using where; Using index |
+----+-------------+-------+------+---------------+------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
這種狀況下,優化器不會使用主鍵,由於主鍵由字段(i1,i2)組成,可是該查詢中沒有引用t2字段;優化器會選擇二級索引 k_d(d) 。性能
咱們將use_index_extensions選項在當前會話開啓,那麼SQL語句的執行計劃會怎樣變化呢?測試
mysql> SET optimizer_switch = 'use_index_extensions=on';
Query OK, 0 rows affected (0.00 sec)
mysql> EXPLAIN
-> SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01';
+----+-------------+-------+------+---------------+------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------------+------+-------------+
| 1 | SIMPLE | t1 | ref | PRIMARY,k_d | k_d | 8 | const,const | 1 | Using index |
+----+-------------+-------+------+---------------+------+---------+-------------+------+-------------+
1 row in set (0.00 sec)
mysql>
當use_index_extensions=off的時候,僅使用索引k_d中d列的數據,忽略了擴展的主鍵列的數據。而use_index_extensions=on時,使用了k_d索引中(i1,i2,d)三列的數據。能夠從上面兩種狀況下的explain輸出結果中信息得以驗證。優化
key_len:由4變到8,說明不單單使用了d列上的索引,並且使用了擴展的主鍵i1列的數據this
ref:由const變爲」const,const」, 使用了索引的兩部分。
rows:從5變爲1,代表InnoDB只須要檢查更少的數據行就能夠產生結果集。
Extra:」Using index,Using where」 變爲」Using index」。經過索引覆蓋就完成數據查詢,而不須要讀取任何的數據行。官方文檔的介紹以下:
The Extra value changes from Using where; Using index to Using index. This means that rows can be read using only the index, without consulting columns in the data row.
其實關於這二者的區別,查了不少資料都沒有完全搞清楚」Using index,Using where」與」Using index」的區別。此處不作展開。
另外,從status信息中「Handler_read_%」相關狀態值能夠觀察實際執行過程當中索引和數據行的訪問統計。
flush table 關閉已打開的數據表,並清除緩存(表緩存和查詢緩存)。
flush status 把status計數器清零。
Handler_read_key:The number of requests to read a row based on a key. If this value is high, it is a good indication that your tables are properly indexed for your queries.
Handler_read_next:The number of requests to read the next row in key order. This value is incremented if you are querying an index column with a range constraint or if you are doing an index scan.(此選項代表在進行索引掃描時,按照索引從數據文件裏取數據的次數。)
關閉use_index_extensions狀況下,status的統計信息
mysql> SET optimizer_switch = 'use_index_extensions=off';
Query OK, 0 rows affected (0.00 sec)
mysql> FLUSH TABLE t1;
Query OK, 0 rows affected (0.00 sec)
mysql> FLUSH STATUS;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01';
+----------+
| COUNT(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
mysql> SHOW STATUS LIKE 'handler_read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 5 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+
7 rows in set (0.00 sec)
開啓use_index_extensions狀況下,status的統計信息
mysql> SET optimizer_switch = 'use_index_extensions=on';
Query OK, 0 rows affected (0.00 sec)
mysql> FLUSH TABLE t1;
Query OK, 0 rows affected (0.00 sec)
mysql> FLUSH STATUS;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01';
+----------+
| COUNT(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
mysql> SHOW STATUS LIKE 'handler_read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 1 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+
7 rows in set (0.00 sec)
mysql>
對比兩個執行計劃,發現Handler_read_next的值從5變爲1,代表索引的訪問效率更高了,減小了數據行的讀取次數。
本文結合官方文檔Use of Index Extensions和本身理解整理。
參考資料:
https://docs.oracle.com/cd/E17952_01/mysql-5.6-en/index-extensions.html
https://dev.mysql.com/doc/refman/5.6/en/index-extensions.html
http://blog.51cto.com/huanghualiang/1557306