CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name
[index_type]
ON tbl_name (key_part,...)
[index_option]
[algorithm_option | lock_option] ...
key_part: {col_name [(length)] | (expr)} [ASC | DESC]
index_option: {
KEY_BLOCK_SIZE [=] value
| index_type
| WITH PARSER parser_name
| COMMENT 'string'
| {VISIBLE | INVISIBLE}
| ENGINE_ATTRIBUTE [=] 'string'
| SECONDARY_ENGINE_ATTRIBUTE [=] 'string'
}
index_type:
USING {BTREE | HASH}
algorithm_option:
ALGORITHM [=] {DEFAULT | INPLACE | COPY}
lock_option:
LOCK [=] {DEFAULT | NONE | SHARED | EXCLUSIVE}
通常,您會在建立表格時使用 CREATE TABLE
在表格上建立所有索引。請參閱第 15.1.20 節,「CREATE TABLE 語法」。對於 InnoDB
表格而言,此指引尤其重要,因為主鍵決定資料檔案中列的實際配置。CREATE INDEX
可讓您將索引新增至現有表格。
CREATE INDEX
會對應到 ALTER TABLE
語法來建立索引。請參閱第 15.1.9 節,「ALTER TABLE 語法」。 CREATE INDEX
無法用於建立 PRIMARY KEY
;請改用 ALTER TABLE
。如需索引的詳細資訊,請參閱第 10.3.1 節,「MySQL 如何使用索引」。
InnoDB
支援虛擬欄位上的次要索引。如需更多資訊,請參閱章節 15.1.20.9,「次要索引和產生欄位」。
當啟用 innodb_stats_persistent
設定時,請在 InnoDB
資料表上建立索引後,執行 ANALYZE TABLE
陳述式。
用於 key_part
規格的 expr
也可以採用 (CAST
的形式,在 json_expression
AS type
ARRAY)JSON
欄位上建立多值索引。請參閱多值索引。
形如 (
的索引規格會建立具有多個索引鍵部分的索引。索引鍵值是將給定索引鍵部分的值串連而成。例如,key_part1
, key_part2
, ...)(col1, col2, col3)
指定一個多欄索引,其索引鍵由 col1
、col2
和 col3
的值組成。
key_part
規格可以 ASC
或 DESC
結尾,以指定索引值是以遞增或遞減順序儲存。如果未指定順序,則預設為遞增。
ASC
和 DESC
不支援用於 HASH
索引、多值索引或 SPATIAL
索引。
以下章節描述 CREATE INDEX
陳述式的不同面向
對於字串欄位,可以使用
語法來指定索引前綴長度,建立僅使用欄位值前導部分的索引col_name
(length
)
對於
BLOB
和TEXT
索引鍵部分,必須指定前綴。此外,BLOB
和TEXT
欄位只能針對InnoDB
、MyISAM
和BLACKHOLE
資料表建立索引。前綴限制以位元組為單位測量。但是,
CREATE TABLE
、ALTER TABLE
和CREATE INDEX
陳述式中索引規格的前綴長度,對於非二進位字串類型(CHAR
、VARCHAR
、TEXT
)解釋為字元數,對於二進位字串類型(BINARY
、VARBINARY
、BLOB
)解釋為位元組數。在為使用多位元組字元集的非二進位字串欄位指定前綴長度時,請考慮這一點。前綴支援和前綴長度(在支援的情況下)取決於儲存引擎。例如,對於使用
REDUNDANT
或COMPACT
列格式的InnoDB
資料表,前綴最長可達 767 個位元組。對於使用DYNAMIC
或COMPRESSED
列格式的InnoDB
資料表,前綴長度限制為 3072 個位元組。對於MyISAM
資料表,前綴長度限制為 1000 個位元組。NDB
儲存引擎不支援前綴(請參閱章節 25.2.7.6,「NDB Cluster 中不支援或遺失的功能」)。
如果指定索引前綴超過最大欄位資料類型大小,CREATE INDEX
會按如下方式處理索引
對於非唯一索引,如果啟用嚴格 SQL 模式,則會發生錯誤;或者,如果未啟用嚴格 SQL 模式,則會將索引長度縮減至最大欄位資料類型大小範圍內,並產生警告。
對於唯一索引,無論 SQL 模式為何,都會發生錯誤,因為縮減索引長度可能會允許插入不符合指定唯一性要求的非唯一項目。
此處顯示的陳述式會使用 name
欄位的前 10 個字元建立索引(假設 name
具有非二進位字串類型)
CREATE INDEX part_of_name ON customer (name(10));
如果欄位中的名稱通常在前 10 個字元中不同,則使用此索引執行的查閱速度不應比使用從整個 name
欄位建立的索引慢太多。此外,對索引使用欄位前綴可以使索引檔案小得多,這可以節省大量磁碟空間,也可能加速 INSERT
操作。
一個「正常」索引索引欄位值或欄位值的前綴。例如,在下表中,給定 t1
列的索引項目包括完整的 col1
值,以及由 col2
值的前 10 個字元組成的前綴
CREATE TABLE t1 (
col1 VARCHAR(10),
col2 VARCHAR(20),
INDEX (col1, col2(10))
);
索引運算式值的功能性索引鍵部分,也可以用來取代欄位或欄位前綴值。使用功能性索引鍵部分可以建立未直接儲存在資料表中的值的索引。範例:
CREATE TABLE t1 (col1 INT, col2 INT, INDEX func_index ((ABS(col1))));
CREATE INDEX idx1 ON t1 ((col1 + col2));
CREATE INDEX idx2 ON t1 ((col1 + col2), (col1 - col2), col1);
ALTER TABLE t1 ADD INDEX ((col1 * 40) DESC);
具有多個索引鍵部分的索引可以混合使用非功能性和功能性索引鍵部分。
功能性索引鍵部分支援 ASC
和 DESC
。
功能性索引鍵部分必須遵守下列規則。如果索引鍵部分定義包含不允許的結構,則會發生錯誤。
在索引定義中,將運算式括在括號內,以區分它們與欄位或欄位前綴。例如,這是允許的;運算式括在括號內
INDEX ((col1 + col2), (col3 - col4))
這會產生錯誤;運算式未括在括號內
INDEX (col1 + col2, col3 - col4)
功能性索引鍵部分不能僅由欄位名稱組成。例如,這是不允許的
INDEX ((col1), (col2))
請改為將索引鍵部分寫成非功能性索引鍵部分,不使用括號
INDEX (col1, col2)
功能性索引鍵部分運算式不能參考欄位前綴。如需解決方法,請參閱本節稍後關於
SUBSTRING()
和CAST()
的討論。功能性索引鍵部分不允許在外部索引鍵規格中使用。
對於 CREATE TABLE ... LIKE
,目標資料表會保留原始資料表中的功能性索引鍵部分。
功能性索引實作為隱藏的虛擬產生欄位,這會產生以下影響
每個功能性索引鍵部分都計入資料表欄位總數的限制;請參閱章節 10.4.7,「資料表欄位數和列大小的限制」。
功能性索引鍵部分會繼承適用於產生欄位的所有限制。範例:
只有允許用於產生欄位的函數才允許用於功能性索引鍵部分。
不允許使用子查詢、參數、變數、預存函數和可載入函數。
如需關於適用限制的更多資訊,請參閱章節 15.1.20.8,「CREATE TABLE 和產生欄位」,以及章節 15.1.9.2,「ALTER TABLE 和產生欄位」。
虛擬產生欄位本身不需要儲存空間。索引本身會佔用儲存空間,如同任何其他索引一樣。
包含函數式索引鍵的索引支援 UNIQUE
。然而,主鍵不能包含函數式索引鍵。主鍵需要儲存產生的欄位,但函數式索引鍵是以虛擬產生的欄位實作,而非儲存產生的欄位。
SPATIAL
和 FULLTEXT
索引不能有函數式索引鍵。
如果資料表沒有主鍵,InnoDB
會自動將第一個 UNIQUE NOT NULL
索引提升為主要索引。對於具有函數式索引鍵的 UNIQUE NOT NULL
索引,則不支援此功能。
如果存在重複的索引,非函數式索引會發出警告。包含函數式索引鍵的索引則沒有此功能。
要移除被函數式索引鍵參考的欄位,必須先移除索引。否則,會發生錯誤。
雖然非函數式索引鍵支援前綴長度規格,但函數式索引鍵則無法做到。解決方案是使用 SUBSTRING()
(或 CAST()
,如本節稍後所述)。若要在查詢中使用包含 SUBSTRING()
函數的函數式索引鍵,則 WHERE
子句必須包含具有相同參數的 SUBSTRING()
。在以下範例中,只有第二個 SELECT
能夠使用索引,因為只有該查詢中的 SUBSTRING()
參數與索引規格相符
CREATE TABLE tbl (
col1 LONGTEXT,
INDEX idx1 ((SUBSTRING(col1, 1, 10)))
);
SELECT * FROM tbl WHERE SUBSTRING(col1, 1, 9) = '123456789';
SELECT * FROM tbl WHERE SUBSTRING(col1, 1, 10) = '1234567890';
函數式索引鍵能夠索引否則無法索引的值,例如 JSON
值。然而,必須正確執行此操作才能達到預期的效果。例如,以下語法無法運作
CREATE TABLE employees (
data JSON,
INDEX ((data->>'$.name'))
);
語法失敗的原因是
->>
運算子會轉換為JSON_UNQUOTE(JSON_EXTRACT(...))
。JSON_UNQUOTE()
會傳回資料類型為LONGTEXT
的值,因此隱藏產生的欄位會被指定相同的資料類型。MySQL 無法索引在索引鍵部分指定時沒有前綴長度的
LONGTEXT
欄位,並且函數式索引鍵不允許使用前綴長度。
若要索引 JSON
欄位,您可以嘗試使用 CAST()
函數,如下所示
CREATE TABLE employees (
data JSON,
INDEX ((CAST(data->>'$.name' AS CHAR(30))))
);
隱藏產生的欄位會被指定 VARCHAR(30)
資料類型,此類型可被索引。但此方法在嘗試使用索引時會產生新問題
CAST()
會傳回排序規則為utf8mb4_0900_ai_ci
(伺服器預設排序規則) 的字串。JSON_UNQUOTE()
會傳回排序規則為utf8mb4_bin
(硬式編碼) 的字串。
因此,在前面的資料表定義中的索引運算式,與下列查詢中的 WHERE
子句運算式之間存在排序規則不符的情況
SELECT * FROM employees WHERE data->>'$.name' = 'James';
索引未被使用,因為查詢和索引中的運算式不同。為了支援函數式索引鍵的這種情況,最佳化工具會在尋找要使用的索引時自動去除 CAST()
,但是只有在索引運算式的排序規則與查詢運算式的排序規則相符時才會這樣做。若要使用具有函數式索引鍵的索引,可以使用以下兩種解決方案中的任一種 (儘管它們的效果有些不同)
解決方案 1:將索引運算式指定為與
JSON_UNQUOTE()
相同的排序規則CREATE TABLE employees ( data JSON, INDEX idx ((CAST(data->>"$.name" AS CHAR(30)) COLLATE utf8mb4_bin)) ); INSERT INTO employees VALUES ('{ "name": "james", "salary": 9000 }'), ('{ "name": "James", "salary": 10000 }'), ('{ "name": "Mary", "salary": 12000 }'), ('{ "name": "Peter", "salary": 8000 }'); SELECT * FROM employees WHERE data->>'$.name' = 'James';
->>
運算子與JSON_UNQUOTE(JSON_EXTRACT(...))
相同,而JSON_UNQUOTE()
會傳回排序規則為utf8mb4_bin
的字串。因此,比較會區分大小寫,且只有一個資料列符合條件+------------------------------------+ | data | +------------------------------------+ | {"name": "James", "salary": 10000} | +------------------------------------+
解決方案 2:在查詢中指定完整運算式
CREATE TABLE employees ( data JSON, INDEX idx ((CAST(data->>"$.name" AS CHAR(30)))) ); INSERT INTO employees VALUES ('{ "name": "james", "salary": 9000 }'), ('{ "name": "James", "salary": 10000 }'), ('{ "name": "Mary", "salary": 12000 }'), ('{ "name": "Peter", "salary": 8000 }'); SELECT * FROM employees WHERE CAST(data->>'$.name' AS CHAR(30)) = 'James';
CAST()
會傳回排序規則為utf8mb4_0900_ai_ci
的字串,因此比較不區分大小寫,且有兩個資料列符合條件+------------------------------------+ | data | +------------------------------------+ | {"name": "james", "salary": 9000} | | {"name": "James", "salary": 10000} | +------------------------------------+
請注意,雖然最佳化工具支援自動去除索引產生的欄位中的 CAST()
,但以下方法無法運作,因為它在使用和不使用索引時會產生不同的結果 (錯誤 #27337092)
mysql> CREATE TABLE employees (
data JSON,
generated_col VARCHAR(30) AS (CAST(data->>'$.name' AS CHAR(30)))
);
Query OK, 0 rows affected, 1 warning (0.03 sec)
mysql> INSERT INTO employees (data)
VALUES ('{"name": "james"}'), ('{"name": "James"}');
Query OK, 2 rows affected, 1 warning (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 1
mysql> SELECT * FROM employees WHERE data->>'$.name' = 'James';
+-------------------+---------------+
| data | generated_col |
+-------------------+---------------+
| {"name": "James"} | James |
+-------------------+---------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE employees ADD INDEX idx (generated_col);
Query OK, 0 rows affected, 1 warning (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 1
mysql> SELECT * FROM employees WHERE data->>'$.name' = 'James';
+-------------------+---------------+
| data | generated_col |
+-------------------+---------------+
| {"name": "james"} | james |
| {"name": "James"} | James |
+-------------------+---------------+
2 rows in set (0.01 sec)
UNIQUE
索引會建立一個限制,使索引中的所有值都必須不同。如果您嘗試新增一個索引鍵值與現有資料列相符的新資料列,則會發生錯誤。如果您在 UNIQUE
索引中為欄位指定前綴值,則欄位值必須在前綴長度內唯一。UNIQUE
索引允許可包含 NULL
的欄位有多個 NULL
值。
如果資料表具有由單一整數類型欄位組成的 PRIMARY KEY
或 UNIQUE NOT NULL
索引,您可以在 SELECT
陳述式中使用 _rowid
來參考索引欄位,如下所示
如果存在由單一整數欄位組成的
PRIMARY KEY
,則_rowid
會參考PRIMARY KEY
欄位。如果存在PRIMARY KEY
但它不是由單一整數欄位組成,則無法使用_rowid
。否則,如果第一個
UNIQUE NOT NULL
索引由單一整數欄位組成,則_rowid
會參考該索引中的欄位。如果第一個UNIQUE NOT NULL
索引不是由單一整數欄位組成,則無法使用_rowid
。
僅 InnoDB
和 MyISAM
資料表支援 FULLTEXT
索引,並且只能包含 CHAR
、VARCHAR
和 TEXT
欄位。索引一律在整個欄位上執行;不支援欄位前綴索引,如果指定任何前綴長度,則會忽略。有關操作的詳細資訊,請參閱 第 14.9 節,〈全文搜尋函數〉。
InnoDB
支援多值索引。多值索引是在儲存值陣列的欄位上定義的次要索引。 「一般」索引對於每個資料記錄有一個索引記錄 (1:1)。多值索引對於單一資料記錄可以有多個索引記錄 (N:1)。多值索引旨在索引 JSON
陣列。例如,在下列 JSON 文件中的郵遞區號陣列上定義的多值索引會為每個郵遞區號建立索引記錄,而每個索引記錄都會參考相同的資料記錄。
{
"user":"Bob",
"user_id":31,
"zipcode":[94477,94536]
}
建立多值索引
您可以在 CREATE TABLE
、ALTER TABLE
或 CREATE INDEX
陳述式中建立多值索引。這需要使用索引定義中的 CAST(... AS ... ARRAY)
,將 JSON
陣列中相同類型的純量值轉換為 SQL 資料類型陣列。接著,會使用 SQL 資料類型陣列中的值,透明地產生虛擬欄位;最後,會在虛擬欄位上建立函數式索引 (也稱為虛擬索引)。定義在 SQL 資料類型陣列值之虛擬欄位上的函數式索引構成了多值索引。
以下清單中的範例顯示在名為 customers
的資料表中,在 JSON
欄位 custinfo
上,以三種不同的方式建立 $.zipcode
陣列的多值索引 zips
。在每種情況下,JSON 陣列都會轉換為 UNSIGNED
整數值的 SQL 資料類型陣列。
只有
CREATE TABLE
CREATE TABLE customers ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, custinfo JSON, INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) ) );
CREATE TABLE
加上ALTER TABLE
CREATE TABLE customers ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, custinfo JSON ); ALTER TABLE customers ADD INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
CREATE TABLE
加上CREATE INDEX
CREATE TABLE customers ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, custinfo JSON ); CREATE INDEX zips ON customers ( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
多值索引也可以定義為複合索引的一部分。此範例顯示一個複合索引,其中包含兩個單值部分 (適用於 id
和 modified
欄位) 和一個多值部分 (適用於 custinfo
欄位)
CREATE TABLE customers (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
custinfo JSON
);
ALTER TABLE customers ADD INDEX comp(id, modified,
(CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
複合索引中只能使用一個多值索引鍵部分。多值索引鍵部分可以相對於索引的其他部分以任何順序使用。換句話說,剛剛顯示的 ALTER TABLE
陳述式可以使用 comp(id, (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY), modified))
(或任何其他順序),並且仍然有效。
使用多值索引
當在 WHERE
子句中指定下列函數時,最佳化工具會使用多值索引來擷取記錄
我們可以透過使用下列 CREATE TABLE
和 INSERT
陳述式建立並填入 customers
資料表來示範此操作
mysql> CREATE TABLE customers (
-> id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-> custinfo JSON
-> );
Query OK, 0 rows affected (0.51 sec)
mysql> INSERT INTO customers VALUES
-> (NULL, NOW(), '{"user":"Jack","user_id":37,"zipcode":[94582,94536]}'),
-> (NULL, NOW(), '{"user":"Jill","user_id":22,"zipcode":[94568,94507,94582]}'),
-> (NULL, NOW(), '{"user":"Bob","user_id":31,"zipcode":[94477,94507]}'),
-> (NULL, NOW(), '{"user":"Mary","user_id":72,"zipcode":[94536]}'),
-> (NULL, NOW(), '{"user":"Ted","user_id":56,"zipcode":[94507,94582]}');
Query OK, 5 rows affected (0.07 sec)
Records: 5 Duplicates: 0 Warnings: 0
首先,我們對 customers
資料表執行三個查詢,每個查詢分別使用 MEMBER OF()
、JSON_CONTAINS()
和 JSON_OVERLAPS()
,並在此處顯示每個查詢的結果
mysql> SELECT * FROM customers
-> WHERE 94507 MEMBER OF(custinfo->'$.zipcode');
+----+---------------------+-------------------------------------------------------------------+
| id | modified | custinfo |
+----+---------------------+-------------------------------------------------------------------+
| 2 | 2019-06-29 22:23:12 | {"user": "Jill", "user_id": 22, "zipcode": [94568, 94507, 94582]} |
| 3 | 2019-06-29 22:23:12 | {"user": "Bob", "user_id": 31, "zipcode": [94477, 94507]} |
| 5 | 2019-06-29 22:23:12 | {"user": "Ted", "user_id": 56, "zipcode": [94507, 94582]} |
+----+---------------------+-------------------------------------------------------------------+
3 rows in set (0.00 sec)
mysql> SELECT * FROM customers
-> WHERE JSON_CONTAINS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+---------------------+-------------------------------------------------------------------+
| id | modified | custinfo |
+----+---------------------+-------------------------------------------------------------------+
| 2 | 2019-06-29 22:23:12 | {"user": "Jill", "user_id": 22, "zipcode": [94568, 94507, 94582]} |
| 5 | 2019-06-29 22:23:12 | {"user": "Ted", "user_id": 56, "zipcode": [94507, 94582]} |
+----+---------------------+-------------------------------------------------------------------+
2 rows in set (0.00 sec)
mysql> SELECT * FROM customers
-> WHERE JSON_OVERLAPS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+---------------------+-------------------------------------------------------------------+
| id | modified | custinfo |
+----+---------------------+-------------------------------------------------------------------+
| 1 | 2019-06-29 22:23:12 | {"user": "Jack", "user_id": 37, "zipcode": [94582, 94536]} |
| 2 | 2019-06-29 22:23:12 | {"user": "Jill", "user_id": 22, "zipcode": [94568, 94507, 94582]} |
| 3 | 2019-06-29 22:23:12 | {"user": "Bob", "user_id": 31, "zipcode": [94477, 94507]} |
| 5 | 2019-06-29 22:23:12 | {"user": "Ted", "user_id": 56, "zipcode": [94507, 94582]} |
+----+---------------------+-------------------------------------------------------------------+
4 rows in set (0.00 sec)
接下來,我們對前三個查詢中的每一個執行 EXPLAIN
mysql> EXPLAIN SELECT * FROM customers
-> WHERE 94507 MEMBER OF(custinfo->'$.zipcode');
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM customers
-> WHERE JSON_CONTAINS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM customers
-> WHERE JSON_OVERLAPS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
剛剛顯示的三個查詢都無法使用任何索引鍵。為了解決此問題,我們可以在 JSON
欄位 (custinfo
) 中的 zipcode
陣列上新增多值索引,如下所示
mysql> ALTER TABLE customers
-> ADD INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
Query OK, 0 rows affected (0.47 sec)
Records: 0 Duplicates: 0 Warnings: 0
當我們再次執行先前的 EXPLAIN
陳述式時,我們現在可以觀察到查詢可以 (並且確實) 使用剛剛建立的索引 zips
mysql> EXPLAIN SELECT * FROM customers
-> WHERE 94507 MEMBER OF(custinfo->'$.zipcode');
+----+-------------+-----------+------------+------+---------------+------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | ref | zips | zips | 9 | const | 1 | 100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM customers
-> WHERE JSON_CONTAINS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | range | zips | zips | 9 | NULL | 6 | 100.00 | Using where |
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM customers
-> WHERE JSON_OVERLAPS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | range | zips | zips | 9 | NULL | 6 | 100.00 | Using where |
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
多值索引可以定義為唯一索引鍵。如果定義為唯一索引鍵,則嘗試插入已存在於多值索引中的值會傳回重複索引鍵錯誤。如果已存在重複值,則嘗試新增唯一多值索引將會失敗,如下所示
mysql> ALTER TABLE customers DROP INDEX zips;
Query OK, 0 rows affected (0.55 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE customers
-> ADD UNIQUE INDEX zips((CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)));
ERROR 1062 (23000): Duplicate entry '[94507, ' for key 'customers.zips'
mysql> ALTER TABLE customers
-> ADD INDEX zips((CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)));
Query OK, 0 rows affected (0.36 sec)
Records: 0 Duplicates: 0 Warnings: 0
多值索引的特性
多值索引具有以下額外特性:
影響多值索引的 DML 操作,其處理方式與影響一般索引的 DML 操作相同,唯一的差別在於單一叢集索引記錄可能會有多個插入或更新操作。
可空性與多值索引
如果多值鍵部分具有空陣列,則不會將任何條目新增至索引,且無法通過索引掃描存取資料記錄。
如果多值鍵部分產生傳回
NULL
值,則會將包含NULL
的單一條目新增至多值索引。如果鍵部分定義為NOT NULL
,則會回報錯誤。如果將型別陣列欄設定為
NULL
,則儲存引擎會儲存指向該資料記錄的單一NULL
記錄。索引陣列中不允許使用
JSON
空值。如果傳回的任何值為NULL
,則會將其視為 JSON 空值,並回報 JSON 值無效錯誤。
由於多值索引是虛擬欄上的虛擬索引,因此它們必須遵守與虛擬產生欄上的次要索引相同的規則。
不會為空陣列新增索引記錄。
多值索引的限制與約束
多值索引受限於此處列出的限制與約束:
每個多值索引只允許一個多值鍵部分。但是,
CAST(... AS ... ARRAY)
運算式可以參考JSON
文件中的多個陣列,如下所示:CAST(data->'$.arr[*][*]' AS UNSIGNED ARRAY)
在此情況下,所有符合 JSON 運算式的值都會以單一扁平陣列的形式儲存在索引中。
具有多值鍵部分的索引不支援排序,因此無法用作主鍵。基於相同原因,無法使用
ASC
或DESC
關鍵字來定義多值索引。多值索引無法是涵蓋索引。
多值索引的每筆記錄最大值數量,取決於單一復原記錄頁面上可儲存的資料量,也就是 65221 個位元組(64K 減去 315 個位元組的額外負荷),這表示索引鍵值的最大總長度也為 65221 個位元組。索引鍵的最大數量取決於各種因素,因此無法定義具體限制。測試顯示,多值索引允許每個記錄最多有 1604 個整數索引鍵。當達到限制時,會回報類似下列的錯誤:ERROR 3905 (HY000): 多值索引 'idx' 的每筆記錄的值數量超出上限,多出 1 個值。
多值鍵部分中唯一允許的運算式類型是
JSON
運算式。此運算式不需要參考插入索引欄中的 JSON 文件中的現有元素,但其本身必須符合語法。由於相同叢集索引記錄的索引記錄分散在整個多值索引中,因此多值索引不支援範圍掃描或僅索引掃描。
不允許在外部索引鍵規格中使用多值索引。
無法為多值索引定義索引字首。
不支援多值索引的線上建立,這表示作業會使用
ALGORITHM=COPY
。請參閱效能與空間需求。多值索引不支援下列兩種字元集與定序組合以外的其他字元集與定序:
具有預設
binary
定序的binary
字元集具有預設
utf8mb4_0900_as_cs
定序的utf8mb4
字元集。
與
InnoDB
表格的欄上其他索引相同,無法使用USING HASH
建立多值索引;嘗試這樣做會產生警告:此儲存引擎不支援 HASH 索引演算法,因此改用儲存引擎預設值。(如同以往一樣支援USING BTREE
)。
MyISAM
、InnoDB
、NDB
和 ARCHIVE
儲存引擎支援空間欄,例如 POINT
和 GEOMETRY
。(第 13.4 節「空間資料類型」說明了空間資料類型。)但是,各個引擎對空間欄索引的支援程度不同。根據下列規則,可使用空間欄上的空間和非空間索引。
空間欄上的空間索引具有以下特性:
空間欄上的非空間索引(使用 INDEX
、UNIQUE
或 PRIMARY KEY
建立)具有以下特性:
在索引鍵部分清單之後,可以提供索引選項。 index_option
值可以是下列任何值:
KEY_BLOCK_SIZE [=]
value
對於
MyISAM
表格,KEY_BLOCK_SIZE
可選擇指定索引鍵區塊所要使用的位元組大小。此值會被視為提示;如有必要,可能會使用不同的大小。為個別索引定義指定的KEY_BLOCK_SIZE
值,會覆寫表格層級的KEY_BLOCK_SIZE
值。對於
InnoDB
表格,索引層級不支援KEY_BLOCK_SIZE
。請參閱第 15.1.20 節「CREATE TABLE 陳述式」。index_type
有些儲存引擎允許您在建立索引時指定索引類型。例如:
CREATE TABLE lookup (id INT) ENGINE = MEMORY; CREATE INDEX id_index ON lookup (id) USING BTREE;
表格 15.1「每個儲存引擎的索引類型」顯示不同儲存引擎支援的允許索引類型值。如果列出多個索引類型,則第一個類型是沒有指定索引類型規範時的預設類型。表格中未列出的儲存引擎,在索引定義中不支援
index_type
子句。無法將
index_type
子句用於FULLTEXT INDEX
規格。全文索引實作與儲存引擎相關。空間索引實作為 R 樹索引。如果您指定的索引類型對給定的儲存引擎無效,但是有另一個引擎可以使用且不影響查詢結果的索引類型,則引擎會使用可用的類型。剖析器會將
RTREE
識別為類型名稱。這僅允許用於SPATIAL
索引。BTREE
索引是由NDB
儲存引擎實作為 T 樹索引。注意對於
NDB
資料表欄上的索引,只能為唯一索引或主鍵指定USING
選項。USING HASH
會防止建立排序索引;否則,在NDB
資料表上建立唯一索引或主鍵,會自動建立排序索引和雜湊索引,各自索引相同的欄集。對於包含
NDB
資料表的一或多個NULL
欄的唯一索引,雜湊索引只能用來查詢常值,這表示IS [NOT] NULL
條件需要完整掃描資料表。其中一種因應措施是,確保在這種資料表上使用一或多個NULL
欄的唯一索引,在建立時一律會包含排序索引;也就是說,在建立索引時避免使用USING HASH
。如果您指定的索引類型對於給定的儲存引擎無效,但有另一種該引擎可以使用且不影響查詢結果的索引類型,則引擎會使用可用的類型。剖析器會將
RTREE
識別為類型名稱,但目前無法為任何儲存引擎指定此類型。注意在
ON
子句之前使用tbl_name
index_type
選項已棄用;預計在未來的 MySQL 版本中會移除對此位置使用選項的支援。如果較早和較後的位置都提供了index_type
選項,則以最後的選項為準。TYPE
被識別為type_name
USING
的同義詞。但是,type_name
USING
是首選形式。下表顯示支援
index_type
選項的儲存引擎的索引特性。表 15.2 InnoDB 儲存引擎索引特性
索引類別 索引類型 儲存 NULL 值 允許重複 NULL 值 IS NULL 掃描類型 IS NOT NULL 掃描類型 主鍵 BTREE
否 否 不適用 不適用 唯一 BTREE
是 是 索引 索引 鍵 BTREE
是 是 索引 索引 FULLTEXT
不適用 是 是 表格 表格 SPATIAL
不適用 否 否 不適用 不適用
表 15.3 MyISAM 儲存引擎索引特性
索引類別 索引類型 儲存 NULL 值 允許重複 NULL 值 IS NULL 掃描類型 IS NOT NULL 掃描類型 主鍵 BTREE
否 否 不適用 不適用 唯一 BTREE
是 是 索引 索引 鍵 BTREE
是 是 索引 索引 FULLTEXT
不適用 是 是 表格 表格 SPATIAL
不適用 否 否 不適用 不適用
表 15.4 MEMORY 儲存引擎索引特性
索引類別 索引類型 儲存 NULL 值 允許重複 NULL 值 IS NULL 掃描類型 IS NOT NULL 掃描類型 主鍵 BTREE
否 否 不適用 不適用 唯一 BTREE
是 是 索引 索引 鍵 BTREE
是 是 索引 索引 主鍵 HASH
否 否 不適用 不適用 唯一 HASH
是 是 索引 索引 鍵 HASH
是 是 索引 索引
表 15.5 NDB 儲存引擎索引特性
索引類別 索引類型 儲存 NULL 值 允許重複 NULL 值 IS NULL 掃描類型 IS NOT NULL 掃描類型 主鍵 BTREE
否 否 索引 索引 唯一 BTREE
是 是 索引 索引 鍵 BTREE
是 是 索引 索引 主鍵 HASH
否 否 表格 (請參閱註 1) 表格 (請參閱註 1) 唯一 HASH
是 是 表格 (請參閱註 1) 表格 (請參閱註 1) 鍵 HASH
是 是 表格 (請參閱註 1) 表格 (請參閱註 1) 表格註解
1.
USING HASH
會阻止建立隱式排序索引。WITH PARSER
parser_name
此選項僅可用於
FULLTEXT
索引。如果全文索引和搜尋操作需要特殊處理,它會將剖析器外掛程式與索引關聯。InnoDB
和MyISAM
支援全文剖析器外掛程式。如果您有一個具有關聯全文剖析器外掛程式的MyISAM
表格,您可以使用ALTER TABLE
將表格轉換為InnoDB
。有關更多資訊,請參閱全文剖析器外掛程式和撰寫全文剖析器外掛程式。COMMENT '
string
'索引定義可以包含最多 1024 個字元的選用註解。
可以使用
MERGE_THRESHOLD
的index_option
COMMENT
子句,為個別索引設定索引頁面的CREATE INDEX
陳述式。例如CREATE TABLE t1 (id INT); CREATE INDEX id_index ON t1 (id) COMMENT 'MERGE_THRESHOLD=40';
如果索引頁面的頁面滿載百分比在刪除列或透過更新操作縮短列時降到
MERGE_THRESHOLD
值以下,InnoDB
會嘗試將索引頁面與相鄰的索引頁面合併。預設MERGE_THRESHOLD
值為 50,即先前硬編碼的值。也可以使用
CREATE TABLE
和ALTER TABLE
陳述式在索引層級和表格層級定義MERGE_THRESHOLD
。有關更多資訊,請參閱第 17.8.11 節,「設定索引頁面的合併閾值」。VISIBLE
、INVISIBLE
指定索引可見性。索引預設為可見。最佳化工具不會使用不可見的索引。索引可見性的規格適用於主鍵(顯式或隱式)以外的索引。有關更多資訊,請參閱第 10.3.12 節,「不可見索引」。
ENGINE_ATTRIBUTE
和SECONDARY_ENGINE_ATTRIBUTE
用於指定主要和次要儲存引擎的索引屬性。這些選項保留供未來使用。指定給此選項的值是一個字串常值,其中包含有效的 JSON 文件或空字串 ('')。無效的 JSON 會被拒絕。
CREATE INDEX i1 ON t1 (c1) ENGINE_ATTRIBUTE='{"key":"value"}';
ENGINE_ATTRIBUTE
和SECONDARY_ENGINE_ATTRIBUTE
值可以重複而不會發生錯誤。在這種情況下,會使用最後指定的值。伺服器不會檢查
ENGINE_ATTRIBUTE
和SECONDARY_ENGINE_ATTRIBUTE
值,而且在變更表格的儲存引擎時也不會清除這些值。
可以給定 ALGORITHM
和 LOCK
子句,以影響表格複製方法以及在修改表格的索引時讀取和寫入表格的並行程度。它們與 ALTER TABLE
陳述式的意義相同。有關更多資訊,請參閱第 15.1.9 節,「ALTER TABLE 陳述式」
NDB Cluster 使用與標準 MySQL Server 相同的 ALGORITHM=INPLACE
語法來支援線上操作。有關更多資訊,請參閱第 25.6.12 節,「在 NDB Cluster 中使用 ALTER TABLE 的線上操作」。