文件首頁
MySQL 8.4 參考手冊
相關文件 下載本手冊
PDF (US Ltr) - 39.9Mb
PDF (A4) - 40.0Mb
Man Pages (TGZ) - 258.5Kb
Man Pages (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 參考手冊  /  ...  /  CREATE INDEX 語法

15.1.15 CREATE INDEX 語法

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) 指定一個多欄索引,其索引鍵由 col1col2col3 的值組成。

key_part 規格可以 ASCDESC 結尾,以指定索引值是以遞增或遞減順序儲存。如果未指定順序,則預設為遞增。

ASCDESC 不支援用於 HASH 索引、多值索引或 SPATIAL 索引。

以下章節描述 CREATE INDEX 陳述式的不同面向

欄位前綴索引鍵部分

對於字串欄位,可以使用 col_name(length) 語法來指定索引前綴長度,建立僅使用欄位值前導部分的索引

如果指定索引前綴超過最大欄位資料類型大小,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);

具有多個索引鍵部分的索引可以混合使用非功能性和功能性索引鍵部分。

功能性索引鍵部分支援 ASCDESC

功能性索引鍵部分必須遵守下列規則。如果索引鍵部分定義包含不允許的結構,則會發生錯誤。

  • 在索引定義中,將運算式括在括號內,以區分它們與欄位或欄位前綴。例如,這是允許的;運算式括在括號內

    INDEX ((col1 + col2), (col3 - col4))

    這會產生錯誤;運算式未括在括號內

    INDEX (col1 + col2, col3 - col4)
  • 功能性索引鍵部分不能僅由欄位名稱組成。例如,這是不允許的

    INDEX ((col1), (col2))

    請改為將索引鍵部分寫成非功能性索引鍵部分,不使用括號

    INDEX (col1, col2)
  • 功能性索引鍵部分運算式不能參考欄位前綴。如需解決方法,請參閱本節稍後關於 SUBSTRING()CAST() 的討論。

  • 功能性索引鍵部分不允許在外部索引鍵規格中使用。

對於 CREATE TABLE ... LIKE,目標資料表會保留原始資料表中的功能性索引鍵部分。

功能性索引實作為隱藏的虛擬產生欄位,這會產生以下影響

包含函數式索引鍵的索引支援 UNIQUE。然而,主鍵不能包含函數式索引鍵。主鍵需要儲存產生的欄位,但函數式索引鍵是以虛擬產生的欄位實作,而非儲存產生的欄位。

SPATIALFULLTEXT 索引不能有函數式索引鍵。

如果資料表沒有主鍵,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 KEYUNIQUE NOT NULL 索引,您可以在 SELECT 陳述式中使用 _rowid 來參考索引欄位,如下所示

  • 如果存在由單一整數欄位組成的 PRIMARY KEY,則 _rowid 會參考 PRIMARY KEY 欄位。如果存在 PRIMARY KEY 但它不是由單一整數欄位組成,則無法使用 _rowid

  • 否則,如果第一個 UNIQUE NOT NULL 索引由單一整數欄位組成,則 _rowid 會參考該索引中的欄位。如果第一個 UNIQUE NOT NULL 索引不是由單一整數欄位組成,則無法使用 _rowid

全文索引

InnoDBMyISAM 資料表支援 FULLTEXT 索引,並且只能包含 CHARVARCHARTEXT 欄位。索引一律在整個欄位上執行;不支援欄位前綴索引,如果指定任何前綴長度,則會忽略。有關操作的詳細資訊,請參閱 第 14.9 節,〈全文搜尋函數〉

多值索引

InnoDB 支援多值索引。多值索引是在儲存值陣列的欄位上定義的次要索引。 一般索引對於每個資料記錄有一個索引記錄 (1:1)。多值索引對於單一資料記錄可以有多個索引記錄 (N:1)。多值索引旨在索引 JSON 陣列。例如,在下列 JSON 文件中的郵遞區號陣列上定義的多值索引會為每個郵遞區號建立索引記錄,而每個索引記錄都會參考相同的資料記錄。

{
    "user":"Bob",
    "user_id":31,
    "zipcode":[94477,94536]
}
建立多值索引

您可以在 CREATE TABLEALTER TABLECREATE 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)) );

多值索引也可以定義為複合索引的一部分。此範例顯示一個複合索引,其中包含兩個單值部分 (適用於 idmodified 欄位) 和一個多值部分 (適用於 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 TABLEINSERT 陳述式建立並填入 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 運算式的值都會以單一扁平陣列的形式儲存在索引中。

  • 具有多值鍵部分的索引不支援排序,因此無法用作主鍵。基於相同原因,無法使用 ASCDESC 關鍵字來定義多值索引。

  • 多值索引無法是涵蓋索引。

  • 多值索引的每筆記錄最大值數量,取決於單一復原記錄頁面上可儲存的資料量,也就是 65221 個位元組(64K 減去 315 個位元組的額外負荷),這表示索引鍵值的最大總長度也為 65221 個位元組。索引鍵的最大數量取決於各種因素,因此無法定義具體限制。測試顯示,多值索引允許每個記錄最多有 1604 個整數索引鍵。當達到限制時,會回報類似下列的錯誤:ERROR 3905 (HY000): 多值索引 'idx' 的每筆記錄的值數量超出上限,多出 1 個值。

  • 多值鍵部分中唯一允許的運算式類型是 JSON 運算式。此運算式不需要參考插入索引欄中的 JSON 文件中的現有元素,但其本身必須符合語法。

  • 由於相同叢集索引記錄的索引記錄分散在整個多值索引中,因此多值索引不支援範圍掃描或僅索引掃描。

  • 不允許在外部索引鍵規格中使用多值索引。

  • 無法為多值索引定義索引字首。

  • 無法在強制轉換為 BINARY 的資料上定義多值索引(請參閱 CAST() 函數的說明)。

  • 不支援多值索引的線上建立,這表示作業會使用 ALGORITHM=COPY。請參閱效能與空間需求

  • 多值索引不支援下列兩種字元集與定序組合以外的其他字元集與定序:

    1. 具有預設 binary 定序的 binary 字元集

    2. 具有預設 utf8mb4_0900_as_cs 定序的 utf8mb4 字元集。

  • InnoDB 表格的欄上其他索引相同,無法使用 USING HASH 建立多值索引;嘗試這樣做會產生警告:此儲存引擎不支援 HASH 索引演算法,因此改用儲存引擎預設值。(如同以往一樣支援 USING BTREE)。

空間索引

MyISAMInnoDBNDBARCHIVE 儲存引擎支援空間欄,例如 POINTGEOMETRY。(第 13.4 節「空間資料類型」說明了空間資料類型。)但是,各個引擎對空間欄索引的支援程度不同。根據下列規則,可使用空間欄上的空間和非空間索引。

空間欄上的空間索引具有以下特性:

  • 僅適用於 InnoDBMyISAM 表格。為其他儲存引擎指定 SPATIAL INDEX 會產生錯誤。

  • 空間欄上的索引必須SPATIAL 索引。因此,對於在空間欄上建立索引,SPATIAL 關鍵字是選用但隱含的。

  • 僅適用於單一空間欄。無法跨多個空間欄建立空間索引。

  • 已編製索引的欄必須為 NOT NULL

  • 禁止欄字首長度。會對每個欄的完整寬度編製索引。

  • 不允許作為主鍵或唯一索引。

空間欄上的非空間索引(使用 INDEXUNIQUEPRIMARY KEY 建立)具有以下特性:

  • 適用於任何支援空間欄的儲存引擎,但 ARCHIVE 除外。

  • 除非索引是主鍵,否則欄可以是 NULL

  • SPATIAL 索引的索引類型取決於儲存引擎。目前使用 B 樹。

  • 僅允許 InnoDBMyISAMMEMORY 表格用於可以有 NULL 值的欄。

索引選項

在索引鍵部分清單之後,可以提供索引選項。 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 子句。

    表格 15.1 每個儲存引擎的索引類型

    儲存引擎 允許的索引類型
    InnoDB BTREE
    MyISAM BTREE
    MEMORY/HEAP HASHBTREE
    NDB HASHBTREE(請參閱內文中的附註)

    無法將 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 索引。如果全文索引和搜尋操作需要特殊處理,它會將剖析器外掛程式與索引關聯。InnoDBMyISAM 支援全文剖析器外掛程式。如果您有一個具有關聯全文剖析器外掛程式的 MyISAM 表格,您可以使用 ALTER TABLE 將表格轉換為 InnoDB。有關更多資訊,請參閱全文剖析器外掛程式撰寫全文剖析器外掛程式

  • COMMENT 'string'

    索引定義可以包含最多 1024 個字元的選用註解。

    可以使用 MERGE_THRESHOLDindex_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 TABLEALTER TABLE 陳述式在索引層級和表格層級定義 MERGE_THRESHOLD。有關更多資訊,請參閱第 17.8.11 節,「設定索引頁面的合併閾值」

  • VISIBLEINVISIBLE

    指定索引可見性。索引預設為可見。最佳化工具不會使用不可見的索引。索引可見性的規格適用於主鍵(顯式或隱式)以外的索引。有關更多資訊,請參閱第 10.3.12 節,「不可見索引」

  • ENGINE_ATTRIBUTESECONDARY_ENGINE_ATTRIBUTE 用於指定主要和次要儲存引擎的索引屬性。這些選項保留供未來使用。

    指定給此選項的值是一個字串常值,其中包含有效的 JSON 文件或空字串 ('')。無效的 JSON 會被拒絕。

    CREATE INDEX i1 ON t1 (c1) ENGINE_ATTRIBUTE='{"key":"value"}';

    ENGINE_ATTRIBUTESECONDARY_ENGINE_ATTRIBUTE 值可以重複而不會發生錯誤。在這種情況下,會使用最後指定的值。

    伺服器不會檢查 ENGINE_ATTRIBUTESECONDARY_ENGINE_ATTRIBUTE 值,而且在變更表格的儲存引擎時也不會清除這些值。

表格複製和鎖定選項

可以給定 ALGORITHMLOCK 子句,以影響表格複製方法以及在修改表格的索引時讀取和寫入表格的並行程度。它們與 ALTER TABLE 陳述式的意義相同。有關更多資訊,請參閱第 15.1.9 節,「ALTER TABLE 陳述式」

NDB Cluster 使用與標準 MySQL Server 相同的 ALGORITHM=INPLACE 語法來支援線上操作。有關更多資訊,請參閱第 25.6.12 節,「在 NDB Cluster 中使用 ALTER TABLE 的線上操作」