文件首頁
MySQL 9.0 參考手冊
相關文件 下載本手冊
PDF (US Ltr) - 40.0Mb
PDF (A4) - 40.1Mb
Man Pages (TGZ) - 258.2Kb
Man Pages (Zip) - 365.3Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 9.0 參考手冊  /  ...  /  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) 語法來指定索引前置長度,以建立僅使用資料行值的前導部分的索引

  • 可以為 CHARVARCHARBINARYVARBINARY 索引鍵部分指定前置詞。

  • 前置詞必須針對 BLOBTEXT 索引鍵部分指定。此外,BLOBTEXT 資料行只能針對 InnoDBMyISAMBLACKHOLE 資料表建立索引。

  • 前置詞限制以位元組為單位測量。但是,CREATE TABLEALTER TABLECREATE INDEX 陳述式中索引規格的前置詞長度,對於非二進位字串類型 (CHARVARCHARTEXT) 會解譯為字元數,而對於二進位字串類型 (BINARYVARBINARYBLOB) 則會解譯為位元組數。在為使用多位元組字元集的非二進位字串資料行指定前置詞長度時,請將此納入考量。

    前置詞支援和前置詞長度 (如果支援) 取決於儲存引擎。例如,對於使用 REDUNDANTCOMPACT 列格式的 InnoDB 資料表,前置詞最多可為 767 個位元組長。對於使用 DYNAMICCOMPRESSED 列格式的 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 值和由前 10 個字元組成的 col2 值的前置詞

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 欄建立索引,您可以嘗試使用 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 索引,則您可以使用 _rowidSELECT 陳述式中參照索引的欄,如下所示:

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

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

全文索引

FULLTEXT 索引僅支援 InnoDBMyISAM 表格,並且只能包含 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 的表格中,在 JSONcustinfo 的陣列 $.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 TABLEALTER 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 TABLECREATE 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 子句中指定下列函數時,最佳化工具會使用多值索引來擷取記錄:

我們可以透過建立和填入 customers 資料表來示範這一點,使用以下 CREATE TABLEINSERT 陳述式:

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 個整數索引鍵。當達到限制時,將會回報類似以下的錯誤:錯誤 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 資料表上建立唯一索引或主鍵會自動導致同時建立已排序索引和雜湊索引,每個索引都會對同一組欄位建立索引。

    對於包含一個或多個 NULL 欄位的 NDB 表格的唯一索引,雜湊索引只能用於查找字面值,這表示 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 索引 索引
    全文檢索 不適用
    空間 不適用 不適用 不適用

    表 15.3 MyISAM 儲存引擎索引特性

    索引類別 索引類型 儲存 NULL 值 允許存在多個 NULL 值 IS NULL 掃描類型 IS NOT NULL 掃描類型
    主鍵 BTREE 不適用 不適用
    唯一 BTREE 索引 索引
    BTREE 索引 索引
    全文檢索 不適用
    空間 不適用 不適用 不適用

    表 15.4 MEMORY 儲存引擎索引特性

    索引類別 索引類型 儲存 NULL 值 允許存在多個 NULL 值 IS NULL 掃描類型 IS NOT NULL 掃描類型
    主鍵 BTREE 不適用 不適用
    唯一 BTREE 索引 索引
    BTREE 索引 索引
    主鍵 雜湊 不適用 不適用
    唯一 雜湊 索引 索引
    雜湊 索引 索引

    表 15.5 NDB 儲存引擎索引特性

    索引類別 索引類型 儲存 NULL 值 允許存在多個 NULL 值 IS NULL 掃描類型 IS NOT NULL 掃描類型
    主鍵 BTREE 索引 索引
    唯一 BTREE 索引 索引
    BTREE 索引 索引
    主鍵 雜湊 表格 (請參閱註 1) 表格 (請參閱註 1)
    唯一 雜湊 表格 (請參閱註 1) 表格 (請參閱註 1)
    雜湊 表格 (請參閱註 1) 表格 (請參閱註 1)

    表格註解

    1. USING HASH 會阻止建立隱含的排序索引。

  • WITH PARSER parser_name

    此選項只能用於 FULLTEXT 索引。如果全文索引和搜尋操作需要特殊處理,它會將解析器外掛程式與索引關聯。InnoDBMyISAM 支援全文解析器外掛程式。如果您的 MyISAM 表格有相關聯的全文解析器外掛程式,您可以使用 ALTER TABLE 將表格轉換為 InnoDB。如需更多資訊,請參閱 全文解析器外掛程式編寫全文解析器外掛程式

  • COMMENT 'string'

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

    可以使用 CREATE INDEX 陳述式的 index_option COMMENT 子句,為個別索引設定索引頁面的 MERGE_THRESHOLD。例如

    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 伺服器相同的 ALGORITHM=INPLACE 語法支援線上作業。如需更多資訊,請參閱 第 25.6.12 節「NDB Cluster 中使用 ALTER TABLE 進行線上作業」