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 集群中不受支持或缺少的功能”)。
如果指定的索引前缀超过最大列数据类型大小,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);
具有多个键部分的索引可以混合使用非函数和函数键部分。
函数键部分支持 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()
,但以下方法无效,因为它在使用和不使用索引时会产生不同的结果(错误 #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
。
仅 InnoDB
和 MyISAM
表支持 FULLTEXT
索引,并且只能包含 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
的表的 JSON
列 custinfo
上的数组 $.zipcode
上创建多值索引 zips
的三种不同方法。在每种情况下,JSON 数组都被转换为 UNSIGNED
整数值的 SQL 数据类型数组。
仅限
CREATE TABLE
CREATE TABLE customers ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, custinfo JSON, INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) ) );
CREATE TABLE
加上ALTER TABLE
CREATE TABLE customers ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, custinfo JSON ); ALTER TABLE customers ADD INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
CREATE TABLE
加上CREATE INDEX
CREATE TABLE customers ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, custinfo JSON ); CREATE INDEX zips ON customers ( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
多值索引也可以定义为复合索引的一部分。此示例显示了一个复合索引,它包含两个单值部分(用于 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
子句。index_type
子句不能用于FULLTEXT INDEX
规范。全文索引实现取决于存储引擎。空间索引实现为 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 服务器相同的 ALGORITHM=INPLACE
语法进行在线操作。有关更多信息,请参见第 25.6.12 节,“NDB Cluster 中使用 ALTER TABLE 进行在线操作”。