CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name
[index_type]
ON tbl_name (key_part,...)
[index_option]
[algorithm_option | lock_option] ...
key_part: {col_name [(length)] | (expr)} [ASC | DESC]
index_option: {
KEY_BLOCK_SIZE [=] value
| index_type
| WITH PARSER parser_name
| COMMENT 'string'
| {VISIBLE | INVISIBLE}
| ENGINE_ATTRIBUTE [=] 'string'
| SECONDARY_ENGINE_ATTRIBUTE [=] 'string'
}
index_type:
USING {BTREE | HASH}
algorithm_option:
ALGORITHM [=] {DEFAULT | INPLACE | COPY}
lock_option:
LOCK [=] {DEFAULT | NONE | SHARED | EXCLUSIVE}
通常,您会在创建表时使用 CREATE TABLE
语句创建表上的所有索引。请参阅 第 15.1.20 节,“CREATE TABLE 语句”。此准则对于 InnoDB
表尤其重要,因为主键决定了数据文件中行的物理布局。 CREATE INDEX
语句使您能够向现有表添加索引。
CREATE INDEX
被映射到 ALTER TABLE
语句以创建索引。请参阅 第 15.1.9 节,“ALTER TABLE 语句”。 CREATE INDEX
不能用于创建 PRIMARY KEY
;请改用 ALTER TABLE
。有关索引的更多信息,请参阅 第 10.3.1 节,“MySQL 如何使用索引”。
InnoDB
支持虚拟列上的二级索引。有关更多信息,请参阅“第 15.1.20.9 节 “二级索引和生成列””。
当启用innodb_stats_persistent
设置时,在为 InnoDB
表创建索引之后,对该表运行 ANALYZE TABLE
语句。
key_part
规范的 expr
还可以采用 (CAST
形式,以便在 json_expression
AS type
ARRAY)JSON
列上创建多值索引。请参阅多值索引。
形式为 (
的索引规范将创建一个包含多个键部分的索引。索引键值是通过连接给定键部分的值而形成的。例如,key_part1
, key_part2
, ...)(col1, col2, col3)
指定了一个多列索引,其索引键由 col1
、col2
和 col3
中的值组成。
key_part
规范可以以 ASC
或 DESC
结尾,以指定索引值是按升序还是降序存储。如果未给出顺序说明符,则默认为升序。
HASH
索引、多值索引或 SPATIAL
索引不支持 ASC
和 DESC
。
以下各节介绍 CREATE INDEX
语句的不同方面
对于字符串列,可以使用
语法创建仅使用列值前导部分的索引,以指定索引前缀长度col_name
(length
)
必须 为
BLOB
和TEXT
键部分指定前缀。此外,只能为InnoDB
、MyISAM
和BLACKHOLE
表对BLOB
和TEXT
列编制索引。前缀限制以字节为单位度量。但是,
CREATE TABLE
、ALTER TABLE
和CREATE INDEX
语句中索引规范的前缀长度对于非二进制字符串类型(CHAR
、VARCHAR
、TEXT
)解释为字符数,对于二进制字符串类型(BINARY
、VARBINARY
、BLOB
)解释为字节数。为使用多字节字符集的非二进制字符串列指定前缀长度时,请考虑到这一点。前缀支持和前缀长度(如果支持)取决于存储引擎。例如,对于使用
REDUNDANT
或COMPACT
行格式的InnoDB
表,前缀最多可以有 767 个字节长。对于使用DYNAMIC
或COMPRESSED
行格式的InnoDB
表,前缀长度限制为 3072 个字节。对于MyISAM
表,前缀长度限制为 1000 个字节。NDB
存储引擎不支持前缀(请参阅“第 25.2.7.6 节 “NDB Cluster 中不支持或缺失的功能””)。
如果指定的索引前缀超过了最大列数据类型大小,则 CREATE INDEX
将按如下方式处理该索引
对于非唯一索引,要么发生错误(如果启用了严格 SQL 模式),要么将索引长度缩短到最大列数据类型大小以内,并生成警告(如果未启用严格 SQL 模式)。
对于唯一索引,无论 SQL 模式如何,都会发生错误,因为减少索引长度可能会导致插入不符合指定唯一性要求的非唯一条目。
此处显示的语句使用 name
列的前 10 个字符创建一个索引(假设 name
的类型为非二进制字符串)
CREATE INDEX part_of_name ON customer (name(10));
如果列中的名称通常在前 10 个字符中不同,则使用此索引执行的查找速度应该不会比使用从整个 name
列创建的索引慢很多。此外,对索引使用列前缀可以使索引文件小得多,这可以节省大量磁盘空间,并且还可以加快 INSERT
操作的速度。
“普通” 索引会对列值或列值的前缀进行索引。例如,在下表中,给定 t1
行的索引条目包括完整的 col1
值和 col2
值的前缀(由其前 10 个字符组成)
CREATE TABLE t1 (
col1 VARCHAR(10),
col2 VARCHAR(20),
INDEX (col1, col2(10))
);
对表达式值进行索引的函数键部分也可以用来代替列或列前缀值。使用函数键部分可以对未直接存储在表中的值进行索引。示例
CREATE TABLE t1 (col1 INT, col2 INT, INDEX func_index ((ABS(col1))));
CREATE INDEX idx1 ON t1 ((col1 + col2));
CREATE INDEX idx2 ON t1 ((col1 + col2), (col1 - col2), col1);
ALTER TABLE t1 ADD INDEX ((col1 * 40) DESC);
具有多个键部分的索引可以混合使用非函数键部分和函数键部分。
函数键部分支持 ASC
和 DESC
。
函数键部分必须遵循以下规则。如果键部分定义包含不允许的构造,则会发生错误。
在索引定义中,将表达式括在括号中,以将它们与列或列前缀区分开来。例如,这是允许的;表达式括在括号中
INDEX ((col1 + col2), (col3 - col4))
这将产生错误;表达式未括在括号中
INDEX (col1 + col2, col3 - col4)
函数键部分不能仅由列名组成。例如,这是不允许的
INDEX ((col1), (col2))
而是将键部分编写为非函数键部分,不带括号
INDEX (col1, col2)
函数键部分表达式不能引用列前缀。有关解决方法,请参阅本节后面有关
SUBSTRING()
和CAST()
的讨论。外键规范中不允许使用函数键部分。
对于 CREATE TABLE ... LIKE
,目标表保留原始表中的函数键部分。
函数索引实现为隐藏的虚拟生成列,这意味着
每个函数键部分都会计入表列总数的限制;请参阅“第 10.4.7 节 “表列数和行大小的限制””。
函数键部分继承适用于生成列的所有限制。示例
只有生成列允许的函数才允许用于函数键部分。
不允许使用子查询、参数、变量、存储函数和可加载函数。
有关适用限制的更多信息,请参阅“第 15.1.20.8 节 “CREATE TABLE 和生成列””和“第 15.1.9.2 节 “ALTER TABLE 和生成列””。
虚拟生成列本身不需要存储空间。索引本身与任何其他索引一样占用存储空间。
包含函数键部分的索引支持 UNIQUE
。但是,主键不能包含函数键部分。主键要求存储生成列,但函数键部分实现为虚拟生成列,而不是存储的生成列。
SPATIAL
和 FULLTEXT
索引不能具有函数键部分。
如果表不包含主键,则 InnoDB
会自动将第一个 UNIQUE NOT NULL
索引提升为主键。具有函数键部分的 UNIQUE NOT NULL
索引不支持此功能。
如果存在重复索引,则非函数索引会引发警告。包含函数键部分的索引没有此功能。
要删除函数键部分引用的列,必须先删除该索引。否则,会发生错误。
虽然非函数键部分支持前缀长度规范,但这对于函数键部分是不可能的。解决方案是使用 SUBSTRING()
(或 CAST()
,如本节后面所述)。要使包含 SUBSTRING()
函数的函数键部分在查询中使用,WHERE
子句必须包含参数相同的 SUBSTRING()
。在以下示例中,只有第二个 SELECT
能够使用该索引,因为这是唯一一个 SUBSTRING()
的参数与索引规范匹配的查询
CREATE TABLE tbl (
col1 LONGTEXT,
INDEX idx1 ((SUBSTRING(col1, 1, 10)))
);
SELECT * FROM tbl WHERE SUBSTRING(col1, 1, 9) = '123456789';
SELECT * FROM tbl WHERE SUBSTRING(col1, 1, 10) = '1234567890';
函数键部分允许对无法以其他方式索引的值(例如 JSON
值)进行索引。但是,必须正确执行此操作才能实现所需的效果。例如,以下语法不起作用
CREATE TABLE employees (
data JSON,
INDEX ((data->>'$.name'))
);
该语法失败,因为
->>
运算符将转换为JSON_UNQUOTE(JSON_EXTRACT(...))
。JSON_UNQUOTE()
返回一个数据类型为LONGTEXT
的值,因此隐藏的生成列也被分配了相同的数据类型。MySQL 无法对键部分未指定前缀长度的
LONGTEXT
列建立索引,并且在函数键部分不允许使用前缀长度。
要为 JSON
列建立索引,您可以尝试使用 CAST()
函数,如下所示
CREATE TABLE employees (
data JSON,
INDEX ((CAST(data->>'$.name' AS CHAR(30))))
);
隐藏的生成列被分配了 VARCHAR(30)
数据类型,可以对其建立索引。但是,在尝试使用索引时,这种方法会产生一个新问题
CAST()
返回一个排序规则为utf8mb4_0900_ai_ci
(服务器默认排序规则)的字符串。JSON_UNQUOTE()
返回一个排序规则为utf8mb4_bin
(硬编码)的字符串。
因此,上一个表定义中的索引表达式与以下查询中的 WHERE
子句表达式之间存在排序规则不匹配
SELECT * FROM employees WHERE data->>'$.name' = 'James';
由于查询和索引中的表达式不同,因此未使用索引。为了支持函数键部分的这种场景,优化器在查找要使用的索引时会自动去除 CAST()
,但前提是索引表达式的排序规则与查询表达式的排序规则匹配。要使用具有函数键部分的索引,以下两种解决方案都可以使用(尽管它们的效果略有不同)
解决方案 1:为索引表达式分配与
JSON_UNQUOTE()
相同的排序规则CREATE TABLE employees ( data JSON, INDEX idx ((CAST(data->>"$.name" AS CHAR(30)) COLLATE utf8mb4_bin)) ); INSERT INTO employees VALUES ('{ "name": "james", "salary": 9000 }'), ('{ "name": "James", "salary": 10000 }'), ('{ "name": "Mary", "salary": 12000 }'), ('{ "name": "Peter", "salary": 8000 }'); SELECT * FROM employees WHERE data->>'$.name' = 'James';
->>
运算符与JSON_UNQUOTE(JSON_EXTRACT(...))
相同,并且JSON_UNQUOTE()
返回一个排序规则为utf8mb4_bin
的字符串。因此,比较区分大小写,并且只有一行匹配+------------------------------------+ | data | +------------------------------------+ | {"name": "James", "salary": 10000} | +------------------------------------+
解决方案 2:在查询中指定完整的表达式
CREATE TABLE employees ( data JSON, INDEX idx ((CAST(data->>"$.name" AS CHAR(30)))) ); INSERT INTO employees VALUES ('{ "name": "james", "salary": 9000 }'), ('{ "name": "James", "salary": 10000 }'), ('{ "name": "Mary", "salary": 12000 }'), ('{ "name": "Peter", "salary": 8000 }'); SELECT * FROM employees WHERE CAST(data->>'$.name' AS CHAR(30)) = 'James';
CAST()
返回一个排序规则为utf8mb4_0900_ai_ci
的字符串,因此比较不区分大小写,并且有两行匹配+------------------------------------+ | data | +------------------------------------+ | {"name": "james", "salary": 9000} | | {"name": "James", "salary": 10000} | +------------------------------------+
请注意,尽管优化器支持使用索引生成列自动去除 CAST()
,但以下方法不起作用,因为它在有索引和没有索引的情况下会产生不同的结果(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
的列,UNIQUE
索引允许多个 NULL
值。
如果一个表具有由单个整数类型的列组成的 PRIMARY KEY
或 UNIQUE NOT NULL
索引,则可以使用 _rowid
在 SELECT
语句中引用该索引列,如下所示
如果存在由单个整数列组成的
PRIMARY KEY
,则_rowid
引用该PRIMARY KEY
列。如果存在PRIMARY KEY
但它不是由单个整数列组成,则不能使用_rowid
。否则,如果第一个
UNIQUE NOT NULL
索引由单个整数列组成,则_rowid
引用该索引中的列。如果第一个UNIQUE NOT NULL
索引不是由单个整数列组成,则不能使用_rowid
。
FULLTEXT
索引仅支持 InnoDB
和 MyISAM
表,并且只能包含 CHAR
、VARCHAR
和 TEXT
列。索引始终基于整个列进行;不支持列前缀索引,并且如果指定了任何前缀长度,则将忽略该长度。有关操作的详细信息,请参阅 第 14.9 节“全文搜索函数”。
InnoDB
支持多值索引。多值索引是在存储值数组的列上定义的二级索引。“普通”索引对于每个数据记录都有一个索引记录 (1:1)。多值索引对于单个数据记录可以有多个索引记录 (N:1)。多值索引旨在对 JSON
数组进行索引。例如,在以下 JSON 文档中的邮政编码数组上定义的多值索引为每个邮政编码创建一个索引记录,其中每个索引记录都引用相同的数据记录。
{
"user":"Bob",
"user_id":31,
"zipcode":[94477,94536]
}
创建多值索引
您可以在 CREATE TABLE
、ALTER TABLE
或 CREATE INDEX
语句中创建多值索引。这需要在索引定义中使用 CAST(... AS ... ARRAY)
,它将 JSON
数组中的相同类型标量值转换为 SQL 数据类型数组。然后,将使用 SQL 数据类型数组中的值透明地生成一个虚拟列;最后,在该虚拟列上创建一个函数索引(也称为虚拟索引)。在由 SQL 数据类型数组中的值组成的虚拟列上定义的函数索引构成了多值索引。
以下列表中的示例展示了三种不同的方法,可以在名为 customers
的表中名为 custinfo
的 JSON
列上的数组 $.zipcode
上创建多值索引 zips
。在每种情况下,JSON 数组都被转换为 UNSIGNED
整数值的 SQL 数据类型数组。
仅限
CREATE TABLE
CREATE TABLE customers ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, custinfo JSON, INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) ) );
CREATE TABLE
加上ALTER TABLE
CREATE TABLE customers ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, custinfo JSON ); ALTER TABLE customers ADD INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
CREATE TABLE
加上CREATE INDEX
CREATE TABLE customers ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, custinfo JSON ); CREATE INDEX zips ON customers ( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
多值索引也可以定义为复合索引的一部分。此示例展示了一个复合索引,它包含两个单值部分(用于 id
和 modified
列)和一个多值部分(用于 custinfo
列)
CREATE TABLE customers (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
custinfo JSON
);
ALTER TABLE customers ADD INDEX comp(id, modified,
(CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
复合索引中只能使用一个多值键部分。多值键部分可以使用相对于键的其他部分的任何顺序。换句话说,刚刚展示的 ALTER TABLE
语句可以使用 comp(id, (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY), modified))
(或任何其他顺序)并且仍然有效。
使用多值索引
当在 WHERE
子句中指定以下函数时,优化器使用多值索引来获取记录
我们可以通过使用以下 CREATE TABLE
和 INSERT
语句创建并填充 customers
表来演示这一点
mysql> CREATE TABLE customers (
-> id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-> custinfo JSON
-> );
Query OK, 0 rows affected (0.51 sec)
mysql> INSERT INTO customers VALUES
-> (NULL, NOW(), '{"user":"Jack","user_id":37,"zipcode":[94582,94536]}'),
-> (NULL, NOW(), '{"user":"Jill","user_id":22,"zipcode":[94568,94507,94582]}'),
-> (NULL, NOW(), '{"user":"Bob","user_id":31,"zipcode":[94477,94507]}'),
-> (NULL, NOW(), '{"user":"Mary","user_id":72,"zipcode":[94536]}'),
-> (NULL, NOW(), '{"user":"Ted","user_id":56,"zipcode":[94507,94582]}');
Query OK, 5 rows affected (0.07 sec)
Records: 5 Duplicates: 0 Warnings: 0
首先,我们对 customers
表执行三个查询,每个查询分别使用 MEMBER OF()
、JSON_CONTAINS()
和 JSON_OVERLAPS()
,每个查询的结果如下所示
mysql> SELECT * FROM customers
-> WHERE 94507 MEMBER OF(custinfo->'$.zipcode');
+----+---------------------+-------------------------------------------------------------------+
| id | modified | custinfo |
+----+---------------------+-------------------------------------------------------------------+
| 2 | 2019-06-29 22:23:12 | {"user": "Jill", "user_id": 22, "zipcode": [94568, 94507, 94582]} |
| 3 | 2019-06-29 22:23:12 | {"user": "Bob", "user_id": 31, "zipcode": [94477, 94507]} |
| 5 | 2019-06-29 22:23:12 | {"user": "Ted", "user_id": 56, "zipcode": [94507, 94582]} |
+----+---------------------+-------------------------------------------------------------------+
3 rows in set (0.00 sec)
mysql> SELECT * FROM customers
-> WHERE JSON_CONTAINS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+---------------------+-------------------------------------------------------------------+
| id | modified | custinfo |
+----+---------------------+-------------------------------------------------------------------+
| 2 | 2019-06-29 22:23:12 | {"user": "Jill", "user_id": 22, "zipcode": [94568, 94507, 94582]} |
| 5 | 2019-06-29 22:23:12 | {"user": "Ted", "user_id": 56, "zipcode": [94507, 94582]} |
+----+---------------------+-------------------------------------------------------------------+
2 rows in set (0.00 sec)
mysql> SELECT * FROM customers
-> WHERE JSON_OVERLAPS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+---------------------+-------------------------------------------------------------------+
| id | modified | custinfo |
+----+---------------------+-------------------------------------------------------------------+
| 1 | 2019-06-29 22:23:12 | {"user": "Jack", "user_id": 37, "zipcode": [94582, 94536]} |
| 2 | 2019-06-29 22:23:12 | {"user": "Jill", "user_id": 22, "zipcode": [94568, 94507, 94582]} |
| 3 | 2019-06-29 22:23:12 | {"user": "Bob", "user_id": 31, "zipcode": [94477, 94507]} |
| 5 | 2019-06-29 22:23:12 | {"user": "Ted", "user_id": 56, "zipcode": [94507, 94582]} |
+----+---------------------+-------------------------------------------------------------------+
4 rows in set (0.00 sec)
接下来,我们对前三个查询运行 EXPLAIN
mysql> EXPLAIN SELECT * FROM customers
-> WHERE 94507 MEMBER OF(custinfo->'$.zipcode');
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM customers
-> WHERE JSON_CONTAINS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM customers
-> WHERE JSON_OVERLAPS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
刚刚展示的三个查询都无法使用任何键。为了解决这个问题,我们可以在 JSON
列(custinfo
)中的 zipcode
数组上添加一个多值索引,如下所示
mysql> ALTER TABLE customers
-> ADD INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
Query OK, 0 rows affected (0.47 sec)
Records: 0 Duplicates: 0 Warnings: 0
当我们再次运行之前的 EXPLAIN
语句时,我们现在可以观察到查询可以使用(并且确实使用了)刚刚创建的索引 zips
mysql> EXPLAIN SELECT * FROM customers
-> WHERE 94507 MEMBER OF(custinfo->'$.zipcode');
+----+-------------+-----------+------------+------+---------------+------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | ref | zips | zips | 9 | const | 1 | 100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM customers
-> WHERE JSON_CONTAINS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | range | zips | zips | 9 | NULL | 6 | 100.00 | Using where |
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM customers
-> WHERE JSON_OVERLAPS(custinfo->'$.zipcode', CAST('[94507,94582]' AS JSON));
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | customers | NULL | range | zips | zips | 9 | NULL | 6 | 100.00 | Using where |
+----+-------------+-----------+------------+-------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
多值索引可以定义为唯一键。如果定义为唯一键,则尝试插入多值索引中已经存在的值将返回重复键错误。如果重复值已经存在,则尝试添加唯一的多个值索引将失败,如下所示
mysql> ALTER TABLE customers DROP INDEX zips;
Query OK, 0 rows affected (0.55 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE customers
-> ADD UNIQUE INDEX zips((CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)));
ERROR 1062 (23000): Duplicate entry '[94507, ' for key 'customers.zips'
mysql> ALTER TABLE customers
-> ADD INDEX zips((CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)));
Query OK, 0 rows affected (0.36 sec)
Records: 0 Duplicates: 0 Warnings: 0
多值索引的特征
多值索引还具有以下特征
影响多值索引的 DML 操作的处理方式与影响普通索引的 DML 操作相同,唯一的区别是对于单个聚簇索引记录可能会有多个插入或更新。
可为空性和多值索引
如果多值键部分有一个空数组,则不会向索引添加任何条目,并且无法通过索引扫描访问数据记录。
如果多值键部分生成返回
NULL
值,则会向多值索引添加一个包含NULL
的条目。如果键部分定义为NOT NULL
,则会报告错误。如果类型化数组列设置为
NULL
,则存储引擎存储一个包含指向数据记录的NULL
的记录。在索引数组中不允许使用
JSON
空值。如果任何返回的值为NULL
,则将其视为 JSON 空值并报告 无效的 JSON 值 错误。
由于多值索引是虚拟列上的虚拟索引,因此它们必须遵守与虚拟生成列上的二级索引相同的规则。
不会为数组添加索引记录。
多值索引的限制
多值索引受以下限制
每个多值索引只允许一个多值键部分。但是,
CAST(... AS ... ARRAY)
表达式可以引用JSON
文档中的多个数组,如下所示CAST(data->'$.arr[*][*]' AS UNSIGNED ARRAY)
在这种情况下,与 JSON 表达式匹配的所有值都作为单个扁平数组存储在索引中。
具有多值键部分的索引不支持排序,因此不能用作主键。出于同样的原因,不能使用
ASC
或DESC
关键字定义多值索引。多值索引不能是覆盖索引。
多值索引的每个记录的最大值数量由单个撤消日志页面上可以存储的数据量决定,即 65221 字节(64K 减去 315 字节的开销),这意味着键值的最大总长度也是 65221 字节。最大键数取决于各种因素,因此无法定义特定的限制。例如,测试表明多值索引允许每个记录有多达 1604 个整数键。当达到限制时,会报告类似于以下内容的错误:错误 3905 (HY000):多值索引“idx”的每个记录的最大值数量超过 1 个值。
多值键部分中唯一允许的表达式类型是
JSON
表达式。该表达式不需要引用插入到索引列的 JSON 文档中的现有元素,但其本身必须在语法上有效。因为同一聚簇索引记录的索引记录分散在整个多值索引中,所以多值索引不支持范围扫描或仅索引扫描。
外键规范中不允许使用多值索引。
不能为多值索引定义索引前缀。
不支持在线创建多值索引,这意味着该操作使用
ALGORITHM=COPY
。请参阅 性能和空间要求。多值索引不支持以下两组字符集和排序规则以外的字符集和排序规则
binary
字符集,使用默认的binary
排序规则utf8mb4
字符集,使用默认的utf8mb4_0900_as_cs
排序规则。
与
InnoDB
表的列上的其他索引一样,不能使用USING HASH
创建多值索引;尝试这样做会导致警告:此存储引擎不支持 HASH 索引算法,已改用存储引擎默认值。(USING BTREE
像往常一样受支持。)
MyISAM
、InnoDB
、NDB
和 ARCHIVE
存储引擎支持空间列,例如 POINT
和 GEOMETRY
。(第 13.4 节“空间数据类型”介绍了空间数据类型。)但是,对空间列索引的支持因引擎而异。空间列上的空间索引和非空间索引可根据以下规则使用。
空间列上的空间索引具有以下特征
空间列上的非空间索引(使用 INDEX
、UNIQUE
或 PRIMARY KEY
创建)具有以下特征
在键部分列表之后,可以给出索引选项。 index_option
值可以是以下任意一项
KEY_BLOCK_SIZE [=]
value
对于
MyISAM
表,KEY_BLOCK_SIZE
可选地指定用于索引键块的大小(以字节为单位)。该值被视为提示;如果需要,可以使用其他大小。为单个索引定义指定的KEY_BLOCK_SIZE
值会覆盖表级别的KEY_BLOCK_SIZE
值。在索引级别,
InnoDB
表不支持KEY_BLOCK_SIZE
。请参阅 第 15.1.20 节“CREATE TABLE 语句”。index_type
某些存储引擎允许您在创建索引时指定索引类型。例如
CREATE TABLE lookup (id INT) ENGINE = MEMORY; CREATE INDEX id_index ON lookup (id) USING BTREE;
表 15.1“每个存储引擎的索引类型” 显示了不同存储引擎支持的允许索引类型值。如果列出了多个索引类型,则在未给出索引类型说明符时,第一个是默认值。表中未列出的存储引擎不支持索引定义中的
index_type
子句。FULLTEXT INDEX
规范不能使用index_type
子句。全文索引实现取决于存储引擎。空间索引实现为 R 树索引。如果指定的索引类型对给定存储引擎无效,但引擎可以使用其他索引类型而不影响查询结果,则引擎将使用可用的类型。解析器将
RTREE
识别为类型名称。这仅允许用于SPATIAL
索引。NDB
存储引擎将BTREE
索引实现为 T 树索引。注意对于
NDB
表列上的索引,USING
选项只能用于唯一索引或主键。USING HASH
会阻止创建有序索引;否则,在NDB
表上创建唯一索引或主键会自动导致创建有序索引和哈希索引,每个索引都索引相同的列集。对于包含
NDB
表的一列或多列NULL
列的唯一索引,哈希索引只能用于查找字面值,这意味着IS [NOT] NULL
条件需要对表进行全面扫描。一种解决方法是确保始终以包含有序索引的方式创建使用此类表上的一列或多列NULL
列的唯一索引;也就是说,在创建索引时避免使用USING HASH
。如果指定的索引类型对给定存储引擎无效,但引擎可以使用其他索引类型而不影响查询结果,则引擎将使用可用的类型。解析器将
RTREE
识别为类型名称,但目前不能为任何存储引擎指定它。注意不建议在
ON
子句之前使用tbl_name
index_type
选项;预计在未来的 MySQL 版本中将删除对此位置中使用该选项的支持。如果在较早和较晚的位置都给出了index_type
选项,则应用最终选项。TYPE
被识别为type_name
USING
的同义词。但是,type_name
USING
是首选形式。下表显示了支持
index_type
选项的存储引擎的索引特征。表 15.2 InnoDB 存储引擎索引特征
索引类 索引类型 存储 NULL 值 允许多个 NULL 值 IS NULL 扫描类型 IS NOT NULL 扫描类型 主键 BTREE
否 否 不适用 不适用 唯一 BTREE
是 是 索引 索引 键 BTREE
是 是 索引 索引 FULLTEXT
不适用 是 是 表 表 SPATIAL
不适用 否 否 不适用 不适用
表 15.3 MyISAM 存储引擎索引特征
索引类 索引类型 存储 NULL 值 允许多个 NULL 值 IS NULL 扫描类型 IS NOT NULL 扫描类型 主键 BTREE
否 否 不适用 不适用 唯一 BTREE
是 是 索引 索引 键 BTREE
是 是 索引 索引 FULLTEXT
不适用 是 是 表 表 SPATIAL
不适用 否 否 不适用 不适用
表 15.4 MEMORY 存储引擎索引特征
索引类 索引类型 存储 NULL 值 允许多个 NULL 值 IS NULL 扫描类型 IS NOT NULL 扫描类型 主键 BTREE
否 否 不适用 不适用 唯一 BTREE
是 是 索引 索引 键 BTREE
是 是 索引 索引 主键 HASH
否 否 不适用 不适用 唯一 HASH
是 是 索引 索引 键 HASH
是 是 索引 索引
表 15.5 NDB 存储引擎索引特征
索引类 索引类型 存储 NULL 值 允许多个 NULL 值 IS NULL 扫描类型 IS NOT NULL 扫描类型 主键 BTREE
否 否 索引 索引 唯一 BTREE
是 是 索引 索引 键 BTREE
是 是 索引 索引 主键 HASH
否 否 表(请参阅注释 1) 表(请参阅注释 1) 唯一 HASH
是 是 表(请参阅注释 1) 表(请参阅注释 1) 键 HASH
是 是 表(请参阅注释 1) 表(请参阅注释 1) 表注释
1.
USING HASH
会阻止创建隐式有序索引。WITH PARSER
parser_name
此选项只能与
FULLTEXT
索引一起使用。如果全文索引和搜索操作需要特殊处理,它会将解析器插件与索引关联。InnoDB
和MyISAM
支持全文解析器插件。如果您有一个与全文解析器插件关联的MyISAM
表,则可以使用ALTER TABLE
将该表转换为InnoDB
。有关更多信息,请参阅 全文解析器插件 和 编写全文解析器插件。COMMENT '
string
'索引定义可以包含最多 1024 个字符的可选注释。
可以使用
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 TABLE
和ALTER TABLE
语句在索引级别和表级别定义MERGE_THRESHOLD
。有关更多信息,请参阅 第 17.8.11 节“配置索引页的合并阈值”。VISIBLE
、INVISIBLE
指定索引可见性。索引默认可见。优化器不使用不可见索引。索引可见性的规范适用于除主键(显式或隐式)之外的索引。有关更多信息,请参见 第 10.3.12 节“不可见索引”。
ENGINE_ATTRIBUTE
和SECONDARY_ENGINE_ATTRIBUTE
用于为主要和次要存储引擎指定索引属性。这些选项保留供将来使用。分配给此选项的值是一个字符串字面量,其中包含有效的 JSON 文档或空字符串 ('')。无效的 JSON 将被拒绝。
CREATE INDEX i1 ON t1 (c1) ENGINE_ATTRIBUTE='{"key":"value"}';
ENGINE_ATTRIBUTE
和SECONDARY_ENGINE_ATTRIBUTE
值可以重复,不会出错。在这种情况下,将使用最后指定的值。服务器不会检查
ENGINE_ATTRIBUTE
和SECONDARY_ENGINE_ATTRIBUTE
值,并且在更改表的存储引擎时也不会清除它们。
可以提供 ALGORITHM
和 LOCK
子句来影响表复制方法以及在修改表索引时读取和写入表的并发级别。它们的含义与 ALTER TABLE
语句相同。有关更多信息,请参见 第 15.1.9 节“ALTER TABLE 语句”
NDB Cluster 支持使用与标准 MySQL Server 相同的 ALGORITHM=INPLACE
语法进行在线操作。有关更多信息,请参见 第 25.6.12 节“使用 ALTER TABLE 在 NDB Cluster 中进行在线操作”。