MySQL InnoDB的二級索引(Secondary Index)會自動補齊主鍵,將主鍵列追加到二級索引列後面。詳細一點來說,InnoDB的二級索引(Secondary Index)除了存儲索引列key值,還存儲著主鍵的值(而不是指向主鍵的指針)。為什麼這樣做呢?因為InnoDB是以聚集索引方式組... ...
MySQL InnoDB的二級索引(Secondary Index)會自動補齊主鍵,將主鍵列追加到二級索引列後面。詳細一點來說,InnoDB的二級索引(Secondary Index)除了存儲索引列key值,還存儲著主鍵的值(而不是指向主鍵的指針)。為什麼這樣做呢?因為InnoDB是以聚集索引方式組織數據的存儲,即主鍵值相鄰的數據行緊湊的存儲在一起(索引組織表)。當數據行移動或者發生頁分裂的時候,可以減少大量的二級索引維護工作。InnoDB移動行時,無需更新二級索引。我們以官方文檔的例子來測試:
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),即包含主鍵值。因此在設計主鍵的時候,常見的一條設計原則是要求主鍵欄位儘量簡短,以避免二級索引過大(因為二級索引會自動補齊主鍵欄位)。
優化器會考慮擴展二級索引的主鍵列,確定什麼時候使用以及如何使用該索引。 這樣可以產生更高效的執行計劃和達到更好的性能。有不少博客介紹索引擴展是從MySQL5.6.9開始引入的。不過個人還沒有在官方文檔看到相關資料。
優化器可以用擴展的二級索引來進行ref,range,index_merge等類型索引訪問(index access),鬆散的索引掃描(index sacns),連接和排序優化,以及min()/max()優化。
我們先來插入測試數據(腳本來自官方文檔):
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開啟、關閉此選項。
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列的數據
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計數器清零。