天天看點

MySQL 8.0新特性:隐藏索引

MySQL 8.0 支援隐藏索引(invisible index),也稱為不可見索引。隐藏索引不會被優化器使用。它允許快速啟用/禁用MySQL Optimizer使用的索引;主鍵不能設定為隐藏(包括顯式設定或隐式設定)。

索引預設是可見的(visible)。使用CREATE TABLE、CREATE INDEX 或ALTER TABLE語句的VISIBLE或者INVISIBLE選項設定一個建立索引的可見性:

mysql >CREATE TABLE t1 (
    ->   i INT,
    ->   j INT,
    ->   k INT,
    ->   INDEX i_idx (i) INVISIBLE
    -> ) ENGINE = InnoDB;
Query OK, 0 rows affected (0.03 sec)
 
mysql >CREATE INDEX j_idx ON t1 (j) INVISIBLE;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0
 
mysql >ALTER TABLE t1 ADD INDEX k_idx (k) INVISIBLE;
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0           

複制

使用ALTER TABLE … ALTER INDEX語句的VISIBLE或者INVISIBLE選項修改已有索引的可見性:

ALTER TABLE t1 ALTER INDEX i_idx INVISIBLE;

ALTER TABLE t1 ALTER INDEX i_idx VISIBLE;

通過資料字典表 INFORMATION_SCHEMA.STATISTICS 或者SHOW INDEX指令可以檢視索引的可見性。例如:

mysql >CREATE TABLE t1 (
    ->   i INT,
    ->   j INT,
    ->   k INT,
    ->   INDEX i_idx (i) INVISIBLE
    -> ) ENGINE = InnoDB;
Query OK, 0 rows affected (0.03 sec)
 
mysql >CREATE INDEX j_idx ON t1 (j) INVISIBLE;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0
 
mysql >ALTER TABLE t1 ADD INDEX k_idx (k) INVISIBLE;
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0           

複制

不可見索引特性可以用于測試删除某個索引對于查詢性能的影響,同時又不需要真正删除索引,也就避免了錯誤删除之後的索引重建。對于一個大表上的索引進行删除重建将會非常耗時,而将其設定為不可見或可見将會非常簡單快捷。

如果某個設定為隐藏的索引實際上仍然需要或者被優化器所使用,可以通過以下多種方法發現缺少該索引帶來的影響:

索引提示中使用了該索引的查詢将會産生錯誤。

性能模式(Performance Schema)中的資料顯示受影響查詢的負載升高。

EXPLAIN 語句顯示了不同的查詢執行計劃。

慢查詢日志中出現了新的查詢語句。

系統變量 optimizer_switch 的 use_invisible_indexes 值控制了優化器建構執行計劃時是否使用隐藏索引。如果設定為 off (預設值),優化器将會忽略隐藏索引(與引入該屬性之前的行為相同)。如果設定為 on,隐藏索引仍然不可見,但是優化器在建構執行計劃時将會考慮這些索引。

使用SET_VAR優化器提示來optimizer_switch臨時更新臨時值 ,可以僅在單個查詢期間啟用不可見索引,如下所示:

mysql >show create table t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `i` int DEFAULT NULL,
  `j` int DEFAULT NULL,
  `k` int DEFAULT NULL,
  KEY `i_idx` (`i`),
  KEY `j_idx` (`j`) /*!80000 INVISIBLE */,
  KEY `k_idx` (`k`) /*!80000 INVISIBLE */
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
 
mysql >EXPLAIN SELECT /*+ SET_VAR(optimizer_switch = 'use_invisible_indexes=on') */ i, j FROM t1 WHERE j >= 2\G   
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 4
     filtered: 33.33
        Extra: Using where
1 row in set, 2 warnings (0.00 sec)
 
mysql >EXPLAIN SELECT i, j FROM t1 WHERE k >= 2\G                                   
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 4
     filtered: 33.33
        Extra: Using where
1 row in set, 1 warning (0.00 sec)           

複制

但是我在MySQL 8.0.23版本的環境測試,好像和官方文檔描述的存在差異,使用使用SET_VAR優化器提示來optimizer_switch臨時更新臨時值的時候出現如下警告,從警告資訊看,optimizer_switch不能設定use_invisible_indexes=on,但是尚未定位到具體的原因,如果有人遇到過,可以留言一起交流;

mysql [email protected]:wjqtest21:02:43>show warnings;
+---------+------+-------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                       |
+---------+------+-------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1231 | Variable 'optimizer_switch' can't be set to the value of 'use_invisible_indexes=on'                                           |
| Note    | 1003 | /* select#1 */ select `wjqtest`.`t1`.`i` AS `i`,`wjqtest`.`t1`.`j` AS `j` from `wjqtest`.`t1` where (`wjqtest`.`t1`.`j` >= 2) |
+---------+------+-------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)           

複制

索引的可見性不會影響索引的維護。例如,無論索引是否可見,每次修改表中的資料時都需要對相應索引進行更新,而且唯一索引都會阻止插入重複的列值。

一個沒有明确定義主鍵的表仍然可能存在有效的隐式主鍵( 表中某些 NOT NULL 字段上建立了 UNIQUE 索引)。在這種情況下,表中第一個這樣的索引和顯式主鍵存在相同的限制效果,該索引也不能被設定為不可見。假如存在以下表定義:

CREATE TABLE t2 (
i INT NOT NULL,
j INT NOT NULL,
UNIQUE j_idx (j)) ENGINE = InnoDB;           

複制

定義中沒有明确指定主鍵,但是 NOT NULL 字段 j 上存在一個唯一索引,它實作了和主鍵相同的資料限制,不能設定為不可見:

mysql> ALTER TABLE t2 ALTER INDEX j_idx INVISIBLE;
ERROR 3522 (HY000): A primary key index cannot be invisible.           

複制

如果為該表指定一個顯式的主鍵:

ALTER TABLE t2 ADD PRIMARY KEY (i);           

複制

該主鍵不能設定為不可見。除此之外,字段 j 上的唯一索引不再是一個隐式的主鍵,是以可以設定為隐藏索引:

mysql> ALTER TABLE t2 ALTER INDEX j_idx INVISIBLE;
Query OK, 0 rows affected (0.03 sec)           

複制

如果我們現在要删除索引,我們可以将其更改為隐藏。但是使用“FORCE / USE INDEX”的查詢怎麼樣?他們是否會抛出一個錯誤?如果強制不存在的索引,你會收到錯誤。你不會看到隐藏索引的錯誤。優化器不會使用它,但知道它存在。

mysql> show create table t1 G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`i` int(11) DEFAULT NULL,
`j` int(11) DEFAULT NULL,
`k` int(11) DEFAULT NULL,
KEY `i_idx` (`i`),
KEY `idx_1` (`i`,`j`,`k`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
 
mysql> explain select * from t1 force index(idx_1) where i=1 and j=4;
+----+-------------+-------+------------+------+---------------+-------+---------+-------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref         | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+-------+---------+-------------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ref  | idx_1         | idx_1 | 10      | const,const |    2 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+-------+---------+-------------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
 
 
mysql> alter table t1 alter index idx_1 invisible;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0
 
mysql> explain select * from t1 force index(idx_1) where i=1 and j=4;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   16 |     6.25 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
 
mysql> explain select * from t1 where i=1 and j=4;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ref  | i_idx         | i_idx | 5       | const |    2 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)           

複制

正如上述結果所示,如果我們使用帶有隐藏索引的“FORCE INDEX”,MySQL會執行全表掃描。MySQL不會抛出任何錯誤,因為索引存在,但它不可見。即使有另一個可用的索引,它也将執行全表掃描。在大型表上,這可能會導緻嚴重的性能問題。即使MySQL在查詢執行期間不抛出任何錯誤,它也應該會在錯誤日志中記錄一個警告。

總結一些對這個新功能的首次使用的想法和總結:

如果你想删除一個索引,但又想事先知道效果。你就可以使它對優化程式不可見。這是一個快速的中繼資料更改,使索引不可見。一旦确定沒有性能下降,就可以真正去删除索引。

關鍵的一點是,隐藏索引不能供優化器使用,但它仍然存在,并通過寫入操作保持最新。即便我們嘗試“FORCE INDEX”,優化器也不會使用它,雖然我認為我們應該能夠在某種程度上強制它。可能會有這樣的情況:

我們可以建立一個新的隐形索引,但如果想要測試它,必須使它可見。這意味着所有對應用程式有即時影響的查詢都将能夠使用它。如果目的隻是想測試它,我不認為這是最好的方法,不是所有人的伺服器上都有相同的資料大小和真實資料。強制隐藏索引這時候可能會很有用。

你有許多索引,但不确定哪一個未使用。你可以将一個索引更改為不可見,以檢視是否存在任何性能下降。如果是,你可以立即更改。

你可能有一個特殊情況,隻有一個查詢可以使用該索引。在這種情況下,隐藏索引可能是一個很好的解決方案。

隐藏索引應用場景:軟删除、灰階釋出。

參考連結

https://dev.mysql.com/doc/refman/8.0/en/invisible-indexes.html