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)
指定了一个多列索引,其中索引键由 col1
、col2
和 col3
的值组成。
key_part
指定可以以 ASC
或 DESC
结尾,指定索引值是否按升序或降序存储。默认是升序,如果不指定顺序specifier。
ASC
和 DESC
不支持 HASH
索引、多值索引或 SPATIAL
索引。
CREATE INDEX
语句的不同方面被描述在以下部分中:
对于字符串列,可以创建使用只使用列值前缀的索引,使用
语法指定索引前缀长度:col_name
(length
)
-
前缀必须指定于
BLOB
和TEXT
键部分。另外,BLOB
和TEXT
列只能在InnoDB
、MyISAM
和BLACKHOLE
表中索引。 -
前缀limits以字节为单位。然而,在
CREATE TABLE
、ALTER TABLE
和CREATE INDEX
语句中的索引指定的前缀长度,对于非二进制字符串类型(CHAR
、VARCHAR
、TEXT
)被解释为字符数,对于二进制字符串类型(BINARY
、VARBINARY
、BLOB
)被解释为字节数。考虑到非二进制字符串列使用多字节字符集时指定前缀长度。前缀支持和前缀长度(如果支持)是存储引擎依赖的。例如,对于使用
InnoDB
表,使用REDUNDANT
或COMPACT
行格式,前缀长度限制为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
操作。
一个“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);
索引可以混合非函数和函数键部分。
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子句必须包含与之相同的参数。在以下示例中,只有第二个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_UNQUOTE()
返回一个数据类型为LONGTEXT
的值,隐藏生成的列因此被分配相同的数据类型。 -
MySQL 无法索引指定无前缀长度的
LONGTEXT
列作为键部分,并且在函数键部分中不允许指定前缀长度。
要索引JSON
列,可以尝试使用以下方式:
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
值出现在可以包含NULL
的列中。
如果表有一个PRIMARY KEY
或UNIQUE NOT NULL
索引,且该索引只有一个整数类型的列,你可以在SELECT
语句中使用_rowid
,如下所示:
-
_rowid
引用了一个只有整数列的PRIMARY KEY
列。如果有一个PRIMARY KEY
但是不包含单个整数列,_rowid
不能用。 -
否则,
_rowid
引用了第一个UNIQUE NOT NULL
索引的列,如果该索引不包含单个整数列,_rowid
不能用。
全文索引
FULLTEXT
索引仅支持在 InnoDB
和 MyISAM
表中,并且只能包含CHAR
、VARCHAR
和TEXT
列。索引总是对整个列进行操作,前缀索引不支持,如果指定了前缀长度将被忽略。详细信息请参阅第14.9节,“全文搜索函数”。
多值索引
InnoDB
支持多值索引。多值索引是定义在一个列上的数组值的次要索引。一个“normal” 索引对每个数据记录都有一个索引记录(1:1)。多值索引可以对同一个数据记录有多个索引记录(N:1)。多值索引旨在为JSON 数组提供索引。例如,对于以下 JSON 文档中的 zip 码数组定义了多值索引,创建了每个 zip 码的索引记录,每个索引记录都引用同一个数据记录。
{
"user":"Bob",
"user_id":31,
"zipcode":[94477,94536]
}
Creating multi-valued 索引
您可以在CREATE TABLE
、ALTER TABLE
或CREATE INDEX
语句中创建多值索引。这需要在索引定义中使用CAST(... AS ... ARRAY)
,将同类型的标量值在JSON
数组转换为SQL数据类型数组。然后,会生成一个虚拟列,其中包含SQL数据类型数组中的值;最后,创建了一个函数索引(也称为虚拟索引),该函数索引是基于SQL数据类型数组中值的虚拟列形成的多值索引。
以下列表中的示例展示了在JSON列custinfo
中创建多值索引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 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))
(或任何其他顺序)仍然有效。
Using multi-valued 索引
优化器使用多值索引来获取记录,以下函数在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
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 表达式的值将被存储到索引中作为一个单一的扁平数组。
-
具有多值键部分的索引不支持排序,因此不能用作主键。同样,多值索引不能使用
ASC
或DESC
关键字定义。 -
多值索引不能是覆盖索引。
-
一个多值索引每条记录的最大值数目由单个undo日志页可以存储的数据量决定,约为65221字节(64K减去315字节的开销),因此键值总长度也不能超过65221字节。键的数量取决于多种因素,无法定义一个特定的限制。测试结果表明,可以在记录中存储1604个整数键,例如。当达到限制时,将报告类似以下错误:ERROR 3905 (HY000): 多值索引‘idx’每条记录的最大值数目已超过
-
多值索引部分中允许的唯一表达式类型是
JSON
表达式。该表达式不需要引用已插入到索引列中的JSON文档元素,但必须是语法正确的。 -
由于同一个聚集索引记录在多值索引中分散存储,多值索引不支持范围扫描或索引只读扫描。
-
不能在外键规范中定义多值索引。
-
不能为多值索引定义索引前缀。
-
不能在线创建多值索引,操作使用
ALGORITHM=COPY
。见性能和空间要求。 -
除了以下两个字符集和排序组合外,其他字符集和排序组合不支持多值索引:
-
二进制字符集与默认的二进制排序
-
utf8mb4 字符集与默认的 utf8mb4_0900_as_cs 排序
-
-
像其他 InnoDB 表中的索引一样,多值索引不能使用
USING 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
值。KEY_BLOCK_SIZE
在InnoDB
表中不受支持。请参阅第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
索引中允许。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
索引一起使用。它将解析器插件与索引关联,如果需要特殊处理的全文检索操作。InnoDB
和MyISAM
支持全文解析器插件。如果您有一个关联了全文解析器插件的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 TABLE
和ALTER TABLE
语句。更多信息,请见第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 集群支持使用标准 MySQL 服务器相同的ALGORITHM=INPLACE
语法进行在线操作。更多信息请见第25.6.12节,“NDB 集群中的ALTER TABLE 在线操作”。