Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 Reference Manual  /  ...  /  CREATE INDEX Statement

15.1.15 创建索引语句

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 规定可以以

形式为 (key_part1, key_part2, ...) 的索引指定创建多个键部分的索引。索引键值由给定的键部分值组成。例如,(col1, col2, col3) 指定了一个多列索引,其中索引键由 col1col2col3 的值组成。

key_part 指定可以以 ASCDESC 结尾,指定索引值是否按升序或降序存储。默认是升序,如果不指定顺序specifier。

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 操作。

一个“normal”索引索引列值或列前缀值。例如,在以下表中,给定 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子句必须包含与之相同的参数。在以下示例中,只有第二个SELECT可以使用索引,因为只有该查询中的参数与索引指定相匹配:

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 列,可以尝试使用以下方式:

CREATE TABLE employees (
  data JSON,
  INDEX ((CAST(data->>'$.name' AS CHAR(30))))
);

隐藏生成的列被分配VARCHAR(30) 数据类型,可以索引。但是,这种方法在尝试使用索引时产生了新的问题:

因此,索引表达式在前一个表定义和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()与生成列索引,但以下方法不起作用,因为它在有索引和无索引时结果不同(Bug#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索引允许多个NULL值出现在可以包含NULL的列中。

如果表有一个PRIMARY KEYUNIQUE NOT NULL索引,且该索引只有一个整数类型的列,你可以在SELECT语句中使用_rowid,如下所示:

  • _rowid引用了一个只有整数列的PRIMARY KEY列。如果有一个PRIMARY KEY但是不包含单个整数列,_rowid不能用。

  • 否则,_rowid引用了第一个UNIQUE NOT NULL索引的列,如果该索引不包含单个整数列,_rowid不能用。

全文索引

FULLTEXT 索引仅支持在 InnoDBMyISAM 表中,并且只能包含CHARVARCHARTEXT 列。索引总是对整个列进行操作,前缀索引不支持,如果指定了前缀长度将被忽略。详细信息请参阅第14.9节,“全文搜索函数”

多值索引

InnoDB 支持多值索引。多值索引是定义在一个列上的数组值的次要索引。一个normal 索引对每个数据记录都有一个索引记录(1:1)。多值索引可以对同一个数据记录有多个索引记录(N:1)。多值索引旨在为JSON 数组提供索引。例如,对于以下 JSON 文档中的 zip 码数组定义了多值索引,创建了每个 zip 码的索引记录,每个索引记录都引用同一个数据记录。

{
    "user":"Bob",
    "user_id":31,
    "zipcode":[94477,94536]
}
Creating multi-valued 索引

您可以在CREATE TABLEALTER TABLECREATE INDEX语句中创建多值索引。这需要在索引定义中使用CAST(... AS ... ARRAY),将同类型的标量值在JSON数组转换为SQL数据类型数组。然后,会生成一个虚拟列,其中包含SQL数据类型数组中的值;最后,创建了一个函数索引(也称为虚拟索引),该函数索引是基于SQL数据类型数组中值的虚拟列形成的多值索引。

以下列表中的示例展示了在JSONcustinfo中创建多值索引zips的三个不同方式,每种情况都将JSON数组转换为SQL数据类型数组UNSIGNED整数值。

  • 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))(或任何其他顺序)仍然有效。

Using multi-valued 索引

优化器使用多值索引来获取记录,以下函数在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
Characteristics of Multi-Valued 索引

多值索引具有以下特征:

  • 影响多值索引的 DML 操作与影响普通索引的 DML 操作一样,只是可能有多个插入或更新一个聚簇索引记录。

  • 空值和多值索引:

    • 如果多值键部分为空数组,索引中不添加任何条目,数据记录不可通过索引扫描访问。

    • 如果多值键部分生成返回 NULL 值,多值索引中添加一个包含 NULL 的条目。如果键部分定义为 NOT NULL,则报告错误。

    • 如果类型数组列设置为 NULL,存储引擎将存储一个包含 NULL 的记录,该记录指向数据记录。

    • JSON 空值不允许在索引数组中。如果返回的任何值是 NULL,将被视为 JSON null 并报告一个 无效 JSON 值 错误。

  • 由于多值索引是虚拟列上的虚拟索引,因此它们必须遵守同样的规则,如虚拟生成列的次要索引。

  • 空数组不添加索引记录。

Limitations and Restrictions on Multi-valued 索引

多值索引受以下限制和约束:

  • 每个多值索引只能有一个多值键部分。然而,CAST(... AS ... ARRAY) 表达式可以引用 JSON 文档中的多个数组,如下所示:

    CAST(data->'$.arr[*][*]' AS UNSIGNED ARRAY)

    在这种情况下,所有匹配 JSON 表达式的值将被存储到索引中作为一个单一的扁平数组。

  • 具有多值键部分的索引不支持排序,因此不能用作主键。同样,多值索引不能使用 ASCDESC 关键字定义。

  • 多值索引不能是覆盖索引。

  • 一个多值索引每条记录的最大值数目由单个undo日志页可以存储的数据量决定,约为65221字节(64K减去315字节的开销),因此键值总长度也不能超过65221字节。键的数量取决于多种因素,无法定义一个特定的限制。测试结果表明,可以在记录中存储1604个整数键,例如。当达到限制时,将报告类似以下错误:ERROR 3905 (HY000): 多值索引‘idx’每条记录的最大值数目已超过

  • 多值索引部分中允许的唯一表达式类型是JSON表达式。该表达式不需要引用已插入到索引列中的JSON文档元素,但必须是语法正确的。

  • 由于同一个聚集索引记录在多值索引中分散存储,多值索引不支持范围扫描或索引只读扫描。

  • 不能在外键规范中定义多值索引。

  • 不能为多值索引定义索引前缀。

  • 不能对BINARY类型的数据(见BINARY)定义多值索引(查看CAST()函数的描述)。

  • 不能在线创建多值索引,操作使用ALGORITHM=COPY。见性能和空间要求

  • 除了以下两个字符集和排序组合外,其他字符集和排序组合不支持多值索引:

    1. 二进制字符集与默认的二进制排序

    2. utf8mb4 字符集与默认的 utf8mb4_0900_as_cs 排序

  • 像其他 InnoDB 表中的索引一样,多值索引不能使用 USING HASH;尝试这样做将会出现警告:这个存储引擎不支持哈希索引算法,使用默认的存储引擎代替。(USING BTREE 如常支持)

空间索引

支持空间列的存储引擎有:MyISAMInnoDBNDBARCHIVE,例如:POINTGEOMETRY。(第13.4节,“空间数据类型”,描述了空间数据类型。)然而,对于引擎的空间列索引支持不同。空间列和非空间列的索引根据以下规则可用。

空间列上的索引具有这些特征:

  • 仅对InnoDBMyISAM 表可用。对其他存储引擎指定SPATIAL INDEX 将导致错误。

  • 空间列上的索引必须SPATIAL 索引。因此,创建空间列索引时,SPATIAL 关键字是可选的隐含关键字。

  • 仅适用于单个空间列。不能创建多个空间列的索引。

  • 索引列必须是NOT NULL

  • 每个列的完整宽度都被索引。

  • 不允许在主键或唯一索引中使用。

空间列上的非空间索引(使用INDEXUNIQUEPRIMARY KEY创建)具有以下特点:

  • ARCHIVE外,所有支持空间列的存储引擎都允许使用。

  • 索引列可以是NULL,除非索引是主键。

  • 非空间索引的索引类型取决于存储引擎。当前使用B树。

  • 允许在InnoDBMyISAMMEMORY表中有NULL值。

索引选项列表后,可以给出索引选项。一个index_option值可以是以下任意一种:

  • KEY_BLOCK_SIZE [=] value

    对于MyISAM表,KEY_BLOCK_SIZE可选地指定索引键块的大小,以字节为单位。该值被视为提示;如果必要,可以使用不同的大小。单个索引定义中的KEY_BLOCK_SIZE值将覆盖表级别的KEY_BLOCK_SIZE值。

    KEY_BLOCK_SIZEInnoDB表中不受支持。请参阅第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 存储引擎支持的索引类型

    Storage Engine Permissible Index Types
    InnoDB BTREE
    MyISAM BTREE
    MEMORY/HEAP HASH, BTREE
    NDB HASH, BTREE (注意)

    不能在FULLTEXT INDEX语句中使用index_type子句。全文索引实现取决于存储引擎。空间索引实现为R树索引。

    如果指定的索引类型对于给定的存储引擎无效,但另一个索引类型可供引擎使用而不影响查询结果,引擎将使用可用的类型。解析器识别RTREE为类型名称。这仅在SPATIAL索引中允许。

    BTREE索引由NDB存储引擎实现为T树索引。

    Note

    对于NDB表中的索引列,USING选项只能用于唯一索引或主键。USING HASH防止创建有序索引;否则,对于NDB表自动创建了一个有序索引和哈希索引,每个索引都索引同一组列。

    对于NDB表的唯一索引,其中包括一个或多个NULL列,哈希索引只能用于查找文字值,这意味着IS [NOT] NULL条件需要对整个表进行全扫描。一个解决方法是确保在创建这样的表的唯一索引时总是包含有序索引;即避免使用USING HASH选项。

    如果指定了不适用于给定存储引擎的索引类型,但该引擎可以使用另一个索引类型而不影响查询结果,引擎将使用可用的类型。解析器识别RTREE作为类型名称,但是当前不能指定任何存储引擎。

    Note

    ON tbl_name 子句之前使用 index_type 选项已经弃用,未来 MySQL 版本中将删除该选项。如果在早期和后期都给出了 index_type 选项,最后一个选项生效。

    TYPE type_name 被识别为 USING type_name 的同义词,但是 USING 是推荐的形式。

    以下表格显示了支持 index_type 选项的存储引擎索引特征。

    第15.2章 InnoDB 存储引擎索引特征

    Index Class Index Type Stores NULL VALUES Permits Multiple NULL Values IS NULL Scan Type IS NOT NULL Scan Type
    Primary key BTREE N/A N/A
    Unique BTREE 索引 索引
    Key BTREE 索引 索引
    FULLTEXT N/A
    SPATIAL N/A N/A N/A

    第15.3章 MyISAM 存储引擎索引特征

    Index Class Index Type Stores NULL VALUES Permits Multiple NULL Values IS NULL Scan Type IS NOT NULL Scan Type
    Primary key BTREE N/A N/A
    Unique BTREE 索引 索引
    Key BTREE 索引 索引
    FULLTEXT N/A
    SPATIAL N/A N/A N/A

    表15.4 内存存储引擎索引特征

    Index Class Index Type Stores NULL VALUES Permits Multiple NULL Values IS NULL Scan Type IS NOT NULL Scan Type
    Primary key BTREE N/A N/A
    Unique BTREE 索引 索引
    Key BTREE 索引 索引
    Primary key HASH N/A N/A
    Unique HASH 索引 索引
    Key HASH 索引 索引

    表15.5 NDB存储引擎索引特征

    Index Class Index Type Stores NULL VALUES Permits Multiple NULL Values IS NULL Scan Type IS NOT NULL Scan Type
    Primary key BTREE 索引 索引
    Unique BTREE 索引 索引
    Key BTREE 索引 索引
    Primary key HASH 表(见注释 1) 表(见注释 1)
    Unique HASH 表(见注释 1) 表(见注释 1)
    Key HASH 表(见注释 1) 表(见注释 1)

    表注释:

    1. USING HASH 防止创建隐式有序索引。

  • WITH PARSER parser_name

    只能与FULLTEXT索引一起使用。它将解析器插件与索引关联,如果需要特殊处理的全文检索操作。 InnoDBMyISAM 支持全文解析器插件。如果您有一个关联了全文解析器插件的MyISAM 表,可以使用 ALTER TABLE 将表转换到 InnoDB。见全文解析器插件编写全文解析器插件 了解更多信息。

  • COMMENT 'string'

    索引定义可以包含可选的注释,长度不超过1024个字符。

    MERGE_THRESHOLD 可以使用CREATE INDEX 语句的 index_option COMMENT 子句来配置索引页的个体值。例如:

    CREATE TABLE t1 (id INT);
    CREATE INDEX id_index ON t1 (id) COMMENT 'MERGE_THRESHOLD=40';

    当索引页的页面满足率低于 MERGE_THRESHOLD 值时,删除或更新操作时,InnoDB 尝试将索引页与邻近索引页合并。默认的 MERGE_THRESHOLD 值为50,之前是硬编码值。

    MERGE_THRESHOLD 也可以在索引级别和表级别使用CREATE TABLEALTER TABLE 语句。更多信息,请见第17.8.11节,“配置索引页合并阈值”

  • VISIBLE, INVISIBLE

    指定索引可见性。索引默认可见。不可见的索引不被优化器使用。索引可见性的指定只应用于非主键索引(包括隐式主键)。更多信息,请参阅第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 集群支持使用标准 MySQL 服务器相同的ALGORITHM=INPLACE语法进行在线操作。更多信息请见第25.6.12节,“NDB 集群中的ALTER TABLE 在线操作”