### 13.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.html).看[第 13.1.20 节，“CREATE TABLE 语句”](create-table.html).该指南对于[`InnoDB`](innodb-storage-engine.html)表，其中主键确定数据文件中行的物理布局。[`创建索引`](create-index.html)使您能够向现有表添加索引。

[`创建索引`](create-index.html)映射到一个[`更改表`](alter-table.html)创建索引的语句。看[第 13.1.9 节，“ALTER TABLE 语句”](alter-table.html).[`创建索引`](create-index.html)不能用于创建`首要的关键`;采用[`更改表`](alter-table.html)反而。有关索引的更多信息，请参阅[第 8.3.1 节，“MySQL 如何使用索引”](mysql-indexes.html).

[`InnoDB`](innodb-storage-engine.html)支持虚拟列的二级索引。有关详细信息，请参阅[第 13.1.20.9 节，“二级索引和生成的列”](create-table-secondary-indexes.html).

当。。。的时候[`innodb_stats_persistent`](innodb-parameters.html#sysvar_innodb_stats_persistent)设置启用，运行[`分析表`](analyze-table.html)声明[`InnoDB`](innodb-storage-engine.html)在该表上创建索引后的表。

从 MySQL 8.0.17 开始，*`表达式`*为一个*`关键部分`*规范可以采取的形式`（投掷 *`json_expression`* 作为 *`类型`* 大批）`在 a 上创建多值索引[`JSON`](json.html)柱子。看[多值指数](create-index.html#create-index-multi-valued).

表格的索引规范`(*`key_part1`*, *`key_part2`*, ...)`创建具有多个关键部分的索引。索引键值是通过连接给定键部分的值形成的。例如`(col1, col2, col3)`指定一个多列索引，其索引键由以下值组成`col1`,`col2`， 和`col3`.

一种*`关键部分`*规范可以以`ASC`要么`DESC`指定索引值是以升序还是降序存储。如果没有给出顺序说明符，则默认为升序。`ASC`和`DESC`不允许`哈希`索引。`ASC`和`DESC`多值索引也不支持。从 MySQL 8.0.12 开始，`ASC`和`DESC`不允许`空间`索引。

以下部分描述了[`创建索引`](create-index.html)陈述：

-   [列前缀键部分](create-index.html#create-index-column-prefixes)

-   [功能关键部件](create-index.html#create-index-functional-key-parts)

-   [唯一索引](create-index.html#create-index-unique)

-   [全文索引](create-index.html#create-index-fulltext)

-   [多值指数](create-index.html#create-index-multi-valued)

-   [空间索引](create-index.html#create-index-spatial)

-   [指数期权](create-index.html#create-index-options)

-   [表复制和锁定选项](create-index.html#create-index-copying)

#### 列前缀键部分

对于字符串列，可以创建仅使用列值的前导部分的索引，使用`*`col_name`*(*`长度`*)`指定索引前缀长度的语法：

-   可以指定前缀[`字符`](char.html), [`VARCHAR`](char.html), [`二进制`](binary-varbinary.html)， 和[`变量`](binary-varbinary.html)关键部位。

-   前缀*必须*被指定为[`斑点`](blob.html)和[`文本`](blob.html)关键部位。此外，[`斑点`](blob.html)和[`文本`](blob.html)列只能为`InnoDB`,`MyISAM`， 和`黑洞`表。

    [](<>)[](<>)[](<>)[](<>)

-   字首*限制*以字节为单位。但是，前缀*长度*对于索引规范[`创建表`](create-table.html),[`更改表`](alter-table.html)， 和[`创建索引`](create-index.html)语句被解释为非二进制字符串类型的字符数 ([`字符`](char.html),[`VARCHAR`](char.html),[`文本`](blob.html)) 和二进制字符串类型的字节数 ([`二进制`](binary-varbinary.html),[`变量`](binary-varbinary.html),[`斑点`](blob.html)）。在为使用多字节字符集的非二进制字符串列指定前缀长度时，请考虑到这一点。

    前缀支持和前缀长度（如果支持）取决于存储引擎。例如，前缀最长可达 767 个字节[`InnoDB`](innodb-storage-engine.html)使用的表`[冗余](glossary.html#glos_redundant_row_format)`要么`[紧凑](glossary.html#glos_compact_row_format)`行格式。前缀长度限制为 3072 字节`InnoDB`使用的表`[动态](glossary.html#glos_dynamic_row_format)`要么`[压缩](glossary.html#glos_compressed_row_format)`行格式。为了[`MyISAM`](myisam-storage-engine.html)表，前缀长度限制为 1000 字节。这[`新开发银行`](mysql-cluster.html)存储引擎不支持前缀（请参阅[第 23.2.7.6 节，“NDB 集群中不支持或缺少的功能”](mysql-cluster-limitations-unsupported.html)）。

    如果指定的索引前缀超过最大列数据类型大小，[`创建索引`](create-index.html)处理索引如下：

-   对于非唯一索引，要么发生错误（如果启用了严格 SQL 模式），要么索引长度减少到最大列数据类型大小内并产生警告（如果未启用严格 SQL 模式）。

-   对于唯一索引，无论 SQL 模式如何都会发生错误，因为减少索引长度可能会导致插入不满足指定唯一性要求的非唯一条目。

    此处显示的语句使用前 10 个字符创建索引`姓名`列（假设`姓名`具有非二进制字符串类型）：


```
CREATE INDEX part_of_name ON customer (name(10));
```

如果列中的名称通常在前 10 个字符中不同，则使用此索引执行的查找应该不会比使用从整个索引创建的索引慢很多`姓名`柱子。此外，为索引使用列前缀可以使索引文件更小，这样可以节省大量磁盘空间，还可以加快速度[`插入`](insert.html)操作。

#### 功能关键部件

“普通”索引索引列值或列值的前缀。例如，在下表中，给定的索引条目`t1`行包括完整的`col1`的值和前缀`col2`由其前 10 个字符组成的值：

```
CREATE TABLE t1 (
  col1 VARCHAR(10),
  col2 VARCHAR(20),
  INDEX (col1, col2(10))
);
```

MySQL 8.0.13 及更高版本支持索引表达式值而不是列或列前缀值的功能键部分。使用功能键部分可以对未直接存储在表中的值进行索引。例子：

```
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)
    ```

-   功能键部分表达式不能引用列前缀。有关解决方法，请参阅[`子串（）`](string-functions.html#function_substring)和[`投掷（）`](cast-functions.html#function_cast)在本节后面。

-   外键规范中不允许使用功能键部分。

    为了[`创建表...喜欢`](create-table-like.html)，目标表保留原始表中的功能键部分。

    功能索引被实现为隐藏的虚拟生成列，具有以下含义：

-   每个功能键部分计入表列总数限制；看[第 8.4.7 节，“表列数和行大小的限制”](column-count-limit.html).

-   功能键部分继承了适用于生成列的所有限制。例子：

    -   功能键部分只允许生成列允许的功能。

    -   不允许使用子查询、参数、变量、存储函数和可加载函数。

        有关适用限制的更多信息，请参阅[第 13.1.20.8 节，“创建表和生成的列”](create-table-generated-columns.html)， 和[第 13.1.9.2 节，“ALTER TABLE 和生成的列”](alter-table-generated-columns.html).

-   虚拟生成的列本身不需要存储。索引本身与任何其他索引一样占用存储空间。

`独特`支持包含功能关键部分的索引。但是，主键不能包含功能键部分。主键需要存储生成的列，但功能键部分实现为虚拟生成列，而不是存储生成列。

`空间`和`全文`索引不能有功能键部分。

如果表不包含主键，`InnoDB`自动提升第一个`唯一非空`主键的索引。这不支持`唯一非空`具有功能关键部分的索引。

如果存在重复索引，非功能性索引会发出警告。包含功能关键部分的索引没有此功能。

要删除功能键部分引用的列，必须先删除索引。否则，会发生错误。

尽管非功能键部分支持前缀长度规范，但这对于功能键部分是不可能的。解决方案是使用[`子串（）`](string-functions.html#function_substring)（要么[`投掷（）`](cast-functions.html#function_cast)，如本节后面所述）。对于包含[`子串（）`](string-functions.html#function_substring)要在查询中使用的函数，`在哪里`子句必须包含[`子串（）`](string-functions.html#function_substring)具有相同的论点。在下面的例子中，只有第二个[`选择`](select.html)能够使用索引，因为这是唯一的查询[`子串（）`](string-functions.html#function_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`](json.html)价值观。但是，必须正确执行此操作才能达到预期的效果。例如，此语法不起作用：

```
CREATE TABLE employees (
  data JSON,
  INDEX ((data->>'$.name'))
);
```

语法失败是因为：

-   这[`->>`](json-search-functions.html#operator_json-inline-path)运算符转换为[`JSON_UNQUOTE(JSON_EXTRACT(...))`](json-modification-functions.html#function_json-unquote).

-   [`JSON_UNQUOTE()`](json-modification-functions.html#function_json-unquote)返回一个数据类型为[`长文`](blob.html)，因此隐藏生成的列被分配了相同的数据类型。

-   MySQL 无法索引[`长文`](blob.html)在键部分指定的列没有前缀长度，并且在功能键部分中不允许使用前缀长度。

    索引`JSON`列，您可以尝试使用[`投掷（）`](cast-functions.html#function_cast)功能如下：


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

隐藏的生成列被分配[`VARCHAR(30)`](char.html)数据类型，可以被索引。但是这种方法在尝试使用索引时会产生一个新问题：

-   [`投掷（）`](cast-functions.html#function_cast)返回一个带有排序规则的字符串`utf8mb4_0900_ai_ci`（服务器默认排序规则）。

-   [`JSON_UNQUOTE()`](json-modification-functions.html#function_json-unquote)返回一个带有排序规则的字符串`utf8mb4_bin`（硬编码）。

    因此，在前面的表定义中的索引表达式和`在哪里`以下查询中的子句表达式：


```
SELECT * FROM employees WHERE data->>'$.name' = 'James';
```

不使用索引是因为查询中的表达式和索引不同。为了支持功能关键部分的这种场景，优化器会自动剥离[`投掷（）`](cast-functions.html#function_cast)在寻找要使用的索引时，但是*只要*如果索引表达式的排序规则与查询表达式的排序规则匹配。对于要使用功能键部分的索引，以下两种解决方案中的任何一种都有效（尽管它们在效果上有所不同）：

-   解决方案1。将索引表达式的排序规则指定为[`JSON_UNQUOTE（）`](json-modification-functions.html#function_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';
    ```

     `演员阵容`返回具有排序规则的字符串`utf8mb4_0900_ai_ci`，因此比较不区分大小写，两行匹配：

    ```
    +------------------------------------+
    | data                               |
    +------------------------------------+
    | {"name": "james", "salary": 9000}  |
    | {"name": "James", "salary": 10000} |
    +------------------------------------+
    ```

    请注意，尽管优化器支持自动剥离[`演员阵容`](cast-functions.html#function_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)
```

#### 唯一索引

A.`唯一的`索引创建一个约束，使得索引中的所有值都必须是不同的。如果尝试添加键值与现有行匹配的新行，则会发生错误。如果为中的列指定前缀值`唯一的`索引，列值在前缀长度内必须是唯一的。A.`唯一的`索引允许多个`无效的`可以包含`无效的`.

[](<>)

如果一张桌子有`主键`或`唯一非空`由具有整数类型的单个列组成的索引，您可以使用`_罗伊德`要引用中的索引列，请执行以下操作：[`选择`](select.html)声明如下：

-   `_罗伊德`指`主键`如果有`主键`由一个整数列组成。如果有`主键`但它不包含单个整数列，`_罗伊德`不能使用。

-   否则`_罗伊德`指第一列中的列`唯一非空`索引，如果该索引由单个整数列组成。如果第一个`唯一非空`索引不包含单个整数列，`_罗伊德`不能使用。

#### 全文索引

`全文`索引只支持[`InnoDB`](innodb-storage-engine.html)和[`米萨姆`](myisam-storage-engine.html)表和只能包含[`烧焦`](char.html), [`瓦尔查尔`](char.html)和[`文本`](blob.html)柱。索引总是发生在整个列上；不支持列前缀索引，如果指定，则忽略任何前缀长度。看见[第12.10节，“全文搜索功能”](fulltext-search.html)，以获取操作细节。

#### 多值索引

[](<>)

从MySQL 8.0.17开始，`InnoDB`支持多值索引。多值索引是在存储值数组的列上定义的二级索引。“正常”索引的每个数据记录都有一个索引记录（1:1）。对于单个数据记录（N:1），多值索引可以有多个索引记录。多值索引用于索引`JSON`数组。例如，在下面的JSON文档中，在邮政编码数组上定义的多值索引为每个邮政编码创建一个索引记录，每个索引记录引用相同的数据记录。

```
{
    "user":"Bob",
    "user_id":31,
    "zipcode":[94477,94536]
}
```

##### 创建多值索引

可以在中创建多值索引[`创建表格`](create-table.html), [`改变桌子`](alter-table.html)或[`创建索引`](create-index.html)陈述这需要使用[`施放（…作为…阵列）`](cast-functions.html#function_cast)在索引定义中，它在`JSON`数组转换为SQL数据类型数组。然后使用SQL数据类型数组中的值透明地生成一个虚拟列；最后，在虚拟列上创建一个函数索引（也称为虚拟索引）。它是在构成多值索引的SQL数据类型数组的虚拟值列上定义的函数索引。

下表中的示例显示了使用多值索引的三种不同方式`拉链`可以在阵列上创建`$zipcode`在`JSON`柱`客户信息`在一个名为`客户`。在每种情况下，JSON数组都转换为`未签名`整数值。

-   `创建表格`只有：

    ```
    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 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 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)) );
    ```

    多值指数也可以定义为综合指数的一部分。此示例显示了一个包含两个单值部分（用于`身份证件`和`被改进的`列）和一个多值部分（用于`客户信息`专栏）：


```
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)) );
```

复合索引中只能使用一个多值键部分。多值密钥部分可以相对于密钥的其他部分以任何顺序使用。换句话说`改变桌子`刚才显示的声明可能会用到`comp（id，（CAST（custinfo->'$.zipcode'作为无符号数组），已修改））`（或任何其他命令）并且仍然有效。

##### 使用多值索引

当在一个函数中指定以下函数时，优化器使用多值索引来获取记录`哪里`条款：

-   [`成员（）`](json-search-functions.html#operator_member-of)

-   [`JSON_CONTAINS（）`](json-search-functions.html#function_json-contains)

-   [`JSON_重叠（）`](json-search-functions.html#function_json-overlaps)

    我们可以通过创建和填充`客户`表中使用了以下内容：`创建表格`和`插入`声明：


```
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
```

首先，我们在数据库上执行三个查询`客户`桌子，一张一张`成员（）`, `JSON_CONTAINS（）`和`JSON_重叠（）`，每个查询的结果如下所示：

```
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.html)关于前三个问题：

```
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)
```

刚才显示的三个查询都不能使用任何键。为了解决这个问题，我们可以在`zipcode`数组中的`JSON`纵队(`客户信息`)，就像这样：

```
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
```

当我们运行上一个`解释`语句，我们现在可以观察到查询可以（并且确实）使用索引`拉链`这是刚刚创建的：

```
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操作相同，唯一的区别是单个聚集索引记录可能有多个插入或更新。

-   可空性和多值索引：

    -   如果多值键部件具有空数组，则不会向索引中添加任何条目，并且索引扫描无法访问数据记录。

    -   如果多值关键部件生成返回`无效的`值，一个包含`无效的`被添加到多值索引中。如果关键部件定义为`非空`，报告了一个错误。

    -   如果类型化数组列设置为`无效的`，存储引擎存储一条包含`无效的`这指向数据记录。

    -   `JSON`索引数组中不允许使用空值。如果返回值为`无效的`，它被视为JSON null，并报告无效的JSON值错误。

-   因为多值索引是虚拟列上的虚拟索引，所以它们必须遵守与虚拟生成列上的二级索引相同的规则。

-   不会为空数组添加索引记录。

##### 多值索引的限制和限制

多值索引受以下限制：

-   每个多值索引只允许一个多值键部分。然而[`施放（…作为…阵列）`](cast-functions.html#function_cast)表达式可以引用一个数组中的多个数组`JSON`文档，如下所示：

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

    在本例中，所有与JSON表达式匹配的值都作为单个平面数组存储在索引中。

-   具有多值键部分的索引不支持排序，因此不能用作主键。出于同样的原因，不能使用`ASC`或`描述`关键词。

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

-   多值索引的每条记录的最大值数由单个撤消日志页上可存储的数据量决定，即65221字节（64K减去315字节的开销），这意味着键值的最大总长度也是65221字节。最大键数取决于各种因素，这会阻止定义特定的限制。例如，测试显示，多值索引允许每个记录最多1604个整数键。当达到限制时，会报告类似于以下内容的错误：错误3905（HY000）：多值索引“idx”的每个记录的最大值超出了1个值。

-   多值键部件中唯一允许的表达式类型是`JSON`表示表达式无需引用插入索引列的JSON文档中的现有元素，但其本身必须在语法上有效。

-   由于同一聚集索引记录的索引记录分散在多值索引中，因此多值索引不支持范围扫描或仅索引扫描。

-   外键规范中不允许使用多值索引。

-   无法为多值索引定义索引前缀。

-   不能在数据类型转换上定义多值索引[`二进制的`](binary-varbinary.html)（请参见[`演员阵容`](cast-functions.html#function_cast)功能）。

-   不支持在线创建多值索引，这意味着该操作使用`算法=复制`看见[性能和空间要求](alter-table.html#alter-table-performance).

-   多值索引不支持以下两种字符集和排序规则组合之外的字符集和排序规则：

    1.  这个`二进制的`具有默认值的字符集`二进制的`校勘

    2.  这个`utf8mb4`具有默认值的字符集`utf8mb4_0900_as_cs`整理。

-   与列上的其他索引一样`InnoDB`表，则无法使用创建多值索引`使用散列`; 尝试这样做会导致警告：此存储引擎不支持哈希索引算法，而是使用了存储引擎默认值。(`使用BTREE`与往常一样受支持。）

#### 空间索引

这个[`米萨姆`](myisam-storage-engine.html), [`InnoDB`](innodb-storage-engine.html), [`NDB`](mysql-cluster.html)和[`档案文件`](archive-storage-engine.html)存储引擎支持空间列，例如[`指向`](spatial-type-overview.html)和[`几何学`](spatial-type-overview.html). ([第11.4节，“空间数据类型”](spatial-types.html)，描述了空间数据类型。）然而，对空间列索引的支持因引擎而异。空间列上的空间索引和非空间索引可根据以下规则使用。

空间列上的空间索引具有以下特征：

-   仅适用于[`InnoDB`](innodb-storage-engine.html)和[`米萨姆`](myisam-storage-engine.html)桌子。指定`空间索引`对于其他存储引擎，将导致错误。

-   从MySQL 8.0.12开始，空间列上的索引*必须*做一个`空间的`指数这个`空间的`因此，关键字是可选的，但对于在空间列上创建索引是隐式的。

-   仅适用于单个空间柱。无法在多个空间列上创建空间索引。

-   索引列必须是`非空`.

-   禁止使用列前缀长度。每列的全宽都被索引。

-   主键或唯一索引不允许使用。

    空间列上的非空间索引（使用`指数`, `唯一的`或`主键`)具有以下特点：

-   允许任何支持空间列的存储引擎，但[`档案文件`](archive-storage-engine.html).

-   列可以是`无效的`除非索引是主键。

-   非索引的索引类型-`空间的`索引取决于存储引擎。目前使用的是B-树。

-   允许用于具有`无效的`价值观只适用于[`InnoDB`](innodb-storage-engine.html), [`米萨姆`](myisam-storage-engine.html)和[`记忆力`](memory-storage-engine.html)桌子。

#### 索引选项

在关键部件列表之后，可以提供索引选项。一*`索引选项`*值可以是以下任意值：

-   `密钥块大小[=]*`价值`*`

    对于[`米萨姆`](myisam-storage-engine.html)桌子，`键块大小`（可选）指定用于索引键块的大小（以字节为单位）。该值被视为提示；如有必要，可以使用不同的尺寸。A.`键块大小`为单个索引定义指定的值将覆盖表级别`键块大小`价值

    `键块大小`在的索引级别不支持[`InnoDB`](innodb-storage-engine.html)桌子。看见[第13.1.20节，“创建表语句”](create-table.html).

-   *`索引类型`*

    某些存储引擎允许您在创建索引时指定索引类型。例如：

    ```
    CREATE TABLE lookup (id INT) ENGINE = MEMORY;
    CREATE INDEX id_index ON lookup (id) USING BTREE;
    ```

    [表13.1，“每个存储引擎的索引类型”](create-index.html#create-index-storage-engine-index-types)显示不同存储引擎支持的允许索引类型值。如果列出了多个索引类型，当没有给出索引类型说明符时，第一个是默认的。表中未列出的存储引擎不支持*`索引类型`*索引定义中的子句。

    [](<>)

    **表13.1每个存储引擎的索引类型**

    | 存储引擎 | 允许的索引类型 |
    | ---- | ------- |
    | [`InnoDB`](innodb-storage-engine.html) | `B树` |
    | [`米萨姆`](myisam-storage-engine.html) | `B树` |
    | [`记忆力`](memory-storage-engine.html)/`堆` | `搞砸`, `B树` |
    | [`NDB`](mysql-cluster.html) | `搞砸`, `B树`（见正文注释） |

    这个*`索引类型`*子句不能用于`全文索引`或者（MySQL 8.0.12之前）`空间索引`规格。全文索引的实现依赖于存储引擎。空间索引实现为R树索引。

    如果指定的索引类型对给定的存储引擎无效，但引擎可以使用另一种索引类型而不影响查询结果，则引擎将使用该可用类型。解析器识别`雷`作为类型名。从MySQL 8.0.12开始，这只允许用于`空间的`索引。8.0.12之前，`雷`无法为任何存储引擎指定。

    `B树`索引由[`NDB`](mysql-cluster.html)存储引擎作为T树索引。

    [](<>)[](<>)

    笔记

    有关上的索引[`NDB`](mysql-cluster.html)表中的列`使用`只能为唯一索引或主键指定选项。`使用散列`防止创建有序索引；否则，请在[`NDB`](mysql-cluster.html)表会自动创建一个有序索引和一个哈希索引，每个索引都对同一组列进行索引。

    用于包含一个或多个索引的唯一索引`无效的`一列[`NDB`](mysql-cluster.html)表中，哈希索引只能用于查找文本值，这意味着`是[不是]空的`条件要求对表格进行全面扫描。一种解决方法是确保使用一个或多个`无效的`这样一个表上的列总是以包含有序索引的方式创建的；也就是说，避免雇佣员工`使用散列`创建索引时。

    如果指定的索引类型对给定的存储引擎无效，但引擎可以使用另一种索引类型而不影响查询结果，则引擎将使用该可用类型。解析器识别`雷`作为类型名，但目前无法为任何存储引擎指定。

    笔记

    的使用*`索引类型`*之前的选项`在 *`tbl_name`*`子句已弃用；期望在未来的 MySQL 版本中删除对在此位置使用该选项的支持。如果*`索引类型`*选项在前面和后面的位置都给出，最后的选项适用。

    `类型 *`类型名称`*`被认为是同义词`使用 *`类型名称`*`.然而，`使用`是首选形式。

    下表显示了支持的存储引擎的索引特征*`索引类型`*选项。

    [](<>)

    **表 13.2 InnoDB 存储引擎索引特征**

    | 索引类 | 索引类型 | 存储空值 | 允许多个 NULL 值 | IS NULL 扫描类型 | IS NOT NULL 扫描类型 |
    | --- | ---- | ---- | ----------- | ------------ | ---------------- |
    | 首要的关键 | `BTREE` | 不 | 不 | 不适用 | 不适用 |
    | 独特 | `BTREE` | 是的 | 是的 | 指数 | 指数 |
    | 钥匙 | `BTREE` | 是的 | 是的 | 指数 | 指数 |
    | `全文` | 不适用 | 是的 | 是的 | 桌子 | 桌子 |
    | `空间` | 不适用 | 不 | 不 | 不适用 | 不适用 |

    [](<>)

    **表 13.3 MyISAM 存储引擎索引特征**

    | 索引类 | 索引类型 | 存储空值 | 允许多个 NULL 值 | IS NULL 扫描类型 | IS NOT NULL 扫描类型 |
    | --- | ---- | ---- | ----------- | ------------ | ---------------- |
    | 首要的关键 | `BTREE` | 不 | 不 | 不适用 | 不适用 |
    | 独特 | `BTREE` | 是的 | 是的 | 指数 | 指数 |
    | 钥匙 | `BTREE` | 是的 | 是的 | 指数 | 指数 |
    | `全文` | 不适用 | 是的 | 是的 | 桌子 | 桌子 |
    | `空间` | 不适用 | 不 | 不 | 不适用 | 不适用 |

    [](<>)

    **表 13.4 MEMORY 存储引擎索引特性**

    | 索引类 | 索引类型 | 存储空值 | 允许多个 NULL 值 | IS NULL 扫描类型 | IS NOT NULL 扫描类型 |
    | --- | ---- | ---- | ----------- | ------------ | ---------------- |
    | 首要的关键 | `BTREE` | 不 | 不 | 不适用 | 不适用 |
    | 独特 | `BTREE` | 是的 | 是的 | 指数 | 指数 |
    | 钥匙 | `BTREE` | 是的 | 是的 | 指数 | 指数 |
    | 首要的关键 | `哈希` | 不 | 不 | 不适用 | 不适用 |
    | 独特 | `哈希` | 是的 | 是的 | 指数 | 指数 |
    | 钥匙 | `哈希` | 是的 | 是的 | 指数 | 指数 |

    [](<>)

    **表 13.5 NDB 存储引擎索引特征**

    | 索引类 | 索引类型 | 存储空值 | 允许多个 NULL 值 | IS NULL 扫描类型 | IS NOT NULL 扫描类型 |
    | --- | ---- | ---- | ----------- | ------------ | ---------------- |
    | 首要的关键 | `BTREE` | 不 | 不 | 指数 | 指数 |
    | 独特 | `BTREE` | 是的 | 是的 | 指数 | 指数 |
    | 钥匙 | `BTREE` | 是的 | 是的 | 指数 | 指数 |
    | 首要的关键 | `哈希` | 不 | 不 | 表（见注1） | 表（见注1） |
    | 独特 | `哈希` | 是的 | 是的 | 表（见注1） | 表（见注1） |
    | 钥匙 | `哈希` | 是的 | 是的 | 表（见注1） | 表（见注1） |

    表注：

    1.  `使用哈希`防止创建隐式有序索引。

-   `带解析器 *`解析器名称`*`

    此选项只能与`全文`索引。如果全文索引和搜索操作需要特殊处理，它将解析器插件与索引相关联。[`InnoDB`](innodb-storage-engine.html)和[`MyISAM`](myisam-storage-engine.html)支持全文解析器插件。如果你有一个[`MyISAM`](myisam-storage-engine.html)具有关联的全文解析器插件的表，您可以将表转换为`InnoDB`使用`更改表`.看[全文解析器插件](https://dev.mysql.com/doc/extending-mysql/8.0/en/plugin-types.html#full-text-plugin-type)和[编写全文解析器插件](https://dev.mysql.com/doc/extending-mysql/8.0/en/writing-full-text-plugins.html)了解更多信息。

-   `评论 '*`细绳`*'`

    索引定义可以包括最多 1024 个字符的可选注释。

    这[`MERGE_THRESHOLD`](index-page-merge-threshold.html)对于索引页面，可以使用*`index_option`* `评论`的条款[`创建索引`](create-index.html)陈述。例如：

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

    如果索引页面的页面完整百分比低于`MERGE_THRESHOLD`删除行或更新操作缩短行时的值，[`InnoDB`](innodb-storage-engine.html)尝试将索引页与相邻的索引页合并。默认`MERGE_THRESHOLD`值为 50，这是之前硬编码的值。

    `MERGE_THRESHOLD`也可以使用在索引级别和表级别定义[`创建表`](create-table.html)和[`更改表`](alter-table.html)陈述。有关详细信息，请参阅[第 15.8.11 节，“为索引页面配置合并阈值”](index-page-merge-threshold.html).

-   `可见的`,`无形的`

    指定索引可见性。默认情况下，索引是可见的。优化器不使用不可见索引。索引可见性规范适用于除主键之外的索引（显式或隐式）。有关详细信息，请参阅[第 8.3.12 节，“不可见索引”](invisible-indexes.html).

    [](<>)

-   `引擎属性`和`SECONDARY_ENGINE_ATTRIBUTE`选项（自 MySQL 8.0.21 起可用）用于指定主存储引擎和辅助存储引擎的索引属性。这些选项保留供将来使用。

    允许的值是包含有效值的字符串文字`JSON`文档或空字符串 ('')。无效的`JSON`被拒绝。

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

    `引擎属性`和`SECONDARY_ENGINE_ATTRIBUTE`值可以重复而不会出错。在这种情况下，使用最后指定的值。

    `引擎属性`和`SECONDARY_ENGINE_ATTRIBUTE`服务器不会检查值，也不会在更改表的存储引擎时清除它们。

#### 表复制和锁定选项

`算法`和`锁`可以给出子句来影响表的复制方法和在修改表的索引时读写表的并发级别。它们的含义与[`更改表`](alter-table.html)陈述。有关详细信息，请参阅[第 13.1.9 节，“ALTER TABLE 语句”](alter-table.html)

NDB Cluster 支持使用相同的在线操作`算法=就地`与标准 MySQL 服务器一起使用的语法。看[第 23.6.11 节，“在 NDB 集群中使用 ALTER TABLE 进行在线操作”](mysql-cluster-online-operations.html)， 了解更多信息。
