基于MySQL8.0.41给出最新语法:
sql
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}
Innodb支持在虚拟列上创建二级索引,支持在JSON类型字段上创建索引。
CREATE INDEX
被映射为 ALTER TABLE
语句,用于创建索引,但是 CREATE INDEX
不能创建 PRIMARY KEY
,只能使用 ALTER TABLE
。
key_part 部分可以使用 ASC
或 DESC
,用来指定索引值是以升序还是降序存储,默认为升序。HASH 索引和多列索引不支持 ASC
和 DESC
。从 MySQL 8.0.12 起,SPATIAL
索引也不支持。
前缀索引
对于字符串列,可以使用 col_name(length) 语法指定索引前缀长度,创建只使用列值前导部分的索引:
- 可以为 CHAR、VARCHAR、BINARY 和 VARBINARY 键部分指定前缀。
- BLOB 和 TEXT 列必须指定前缀创建索引。
- 前缀限制以字节为单位。但在
CREATE TABLE
、ALTER TABLE
和CREATE INDEX
语句中,索引规范的前缀长度对于非二进制字符串类型(CHAR、VARCHAR、TEXT)解释为 字符数 ,对于二进制字符串类型(BINARY、VARBINARY、BLOB)解释为 字节数。 - 前缀支持的长度取决于存储引擎。对于使用
REDUNDANT(冗余)
或COMPACT(紧凑)
行格式的 InnoDB 表,前缀长度最多为 767 字节。对于DYNAMIC(动态)
或COMPRESSED(压缩)
表,前缀长度限制为 3072 字节。对于 MyISAM 表,前缀长度限制为 1000 字节。
如果指定的索引前缀超过了列数据类型的最大大小,CREATE INDEX 会按以下方式处理该索引:
- 对于非唯一索引,如果启用了严格 SQL 模式,会报错,如果未启用严格 SQL 模式,索引长度会被缩减到最大列数据类型大小范围内并给出警告。
- 对于唯一索引,无论 SQL 模式如何,都会报错,因为减少索引长度没办法保证数据唯一性。
举个栗子,给t_user_order表中的字段uid创建前缀索引,指定长度是5:
sql
create index idx_uid_prefix on t_user_order(uid(5))
执行查询SQL:
csharp
explain select * from t_user_order where uid = '9a4aebe012384c109106504b7cff14b3';
输出结果:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | t_user_order | ref | idx_uid_prefix | idx_uid_prefix | 22 | const | 30 | 100 | Using where |
同样的数据表,我们改成创建正常的索引:
csharp
create index idx_uid on t_user_order (uid)
执行相同的SQL:
csharp
explain select * from t_user_order where uid = '9a4aebe012384c109106504b7cff14b3';
输出结果:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | t_user_order | ref | idx_uid | idx_uid | 258 | const | 30 | 100 |
对比可以发现,首先前缀索引的ken_len值小于正常索引的ken_len, ken_len表示 查询实际使用的索引部分所占的字节长度,uid字段是varchar类型,且在utf8mb4编码下占用空间是每个字符串4个字节,创建的前缀索引长度是5,再加上可变长类型的实际长度前缀(1-2字节), 4 x 5 + 1 = 22,小于正常索引的258,索引字段使用率低。
其次,前缀索引的extra
列显示了using where
,表示MySQL服务器层在存储引擎返回行数据之后又进行了额外的过滤;正常索引的extra
列是空,则没有额外的处理工作。
所以可以得出结论,前缀索引的性能在使用等值查询时,性能明显弱于正常索引匹配,如果列中的值大部分情况下只有前 5 个字符不同,那么使用前缀索引和正常索引的性能差距就很小。此外,使用前缀索引可以使索引文件更小,从而节省大量磁盘空间,并可能加快 INSERT 操作速度。
函数索引
举个栗子,我们先正常的创建一个表,并且附带一个联合索引:
sql
CREATE TABLE t_index (
col1 VARCHAR(10),
col2 VARCHAR(20),
INDEX (col1, col2(10))
);
MySQL 8.0.13 及更高版本支持创建函数索引,可以为不直接存储在表中的值建立索引,且支持ASC和DESC,举个栗子:
sql
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);
创建函数索引必须遵守以下规则,否则会报错:
- 在创建索引时,表达式必须在括号里,比如:
sql
INDEX ((col1 + col2), (col3 - col4))
错误的栗子会报错:
sql
INDEX (col1 + col2, col3 - col4)
- 索引部分不能只由列名组成,错误的栗子:
sql
INDEX ((col1), (col2))
正确的:
sql
INDEX (col1, col2)
- 函数索引不能使用列前缀,但是可以通过 SUBSTRING函数支持。 举个例子:
sql
CREATE TABLE tbl (
col1 LONGTEXT,
INDEX idx1 ((SUBSTRING(col1, 1, 10)))
);
创建tbl表,定义了col1字段,然后使用substring函数创建函数索引,接下来给这个表录入一千条数据:
sql
INSERT INTO tbl (col1)
SELECT
CONCAT(
MD5(RAND()), -- 32 位随机哈希
MD5(RAND()), -- 再拼接 32 位
MD5(RAND()) -- 总长度 96 位
) AS random_text
FROM (
WITH RECURSIVE numbers (n) AS (
SELECT 1 -- 初始值
UNION ALL
SELECT n + 1 FROM numbers -- 递归增加
WHERE n < 1000 -- 停止条件(生成 1000 行)
)
SELECT n FROM numbers
) AS seq;
给出两个查询的栗子:
sql
SELECT * FROM tbl WHERE SUBSTRING(col1, 1, 9) = '2afc069d4';
select * from tbl where substring(col1, 1, 10) = '2afc069d47';
上面两个SQL,只有第二个SQL能用到索引,使用函数索引时的参数必须和定义索引的参数是相同的,执行计划如下:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | tbl | ref | idx1 | idx1 | 43 | const | 1 | 100 |
- 函数索引部分不能使用外键列。
函数索引是以隐藏虚拟生成列的形式实现的,因此会产生这些影响:
- 每个函数索引部分都计算在表列总数限制内。
- 不允许使用子查询、参数、变量、存储过程和随机值函数。
- 虚拟生成列本身不需要存储空间。索引本身与其他索引一样占用存储空间。
unique索引可以使用函数索引来定义,但是Primary
、SPATIAL
、FULLTEXT
不支持使用函数式索引创建,举个栗子:
sql
alter table t_i add unique index uniq_func((col1 + 1))
函数式索引在JSON字段上可能比较常用,因为JSON字段值通常比较复杂,如果要针对JSON字段中的某个属性创建索引的话,需要注意语法,举个容易出错的栗子:
sql
CREATE TABLE employees (
data JSON,
INDEX ((data->>'$.name'))
);
语法要求:
- ->> 操作符需要转换为 JSON_UNQUOTE(JSON_EXTRACT(...))
- JSON_UNQUOTE() 返回的值的数据类型为 LONGTEXT,所以隐藏生成的列也是相同的数据类型。
- 直接对返回
TEXT
/BLOB
类型的 JSON 提取值创建索引时,需通过CAST
明确转换为字符类型
所以上面的SQL语句,可以改成这样:
sql
CREATE TABLE employees (
data JSON,
INDEX idx_name ((CAST(json_unquote(json_extract(data, '$.name')) AS CHAR(255))))
);
或者这样:
sql
CREATE TABLE employees (
data JSON,
INDEX ((CAST(data->>'$.name' AS CHAR(30))))
);
但是有两个需要注意的问题:
- CAST() 返回的字符串是使用的 utf8mb4_0900_ai_ci(默认)排序规则。
- JSON_UNQUOTE() 返回字符串是使用的 utf8mb4_bin(硬编码)排序规则。
所以上面定义的表,如果用下面SQL执行的话,不会命中索引:
sql
SELECT * FROM employees WHERE data->>'$.name' = 'Sdd2cfb096f';
因为查询中的表达式与索引中的表达式不同,索引不会使用。为了支持这种情况,优化器在查找要使用的索引时会自动删除 CAST(),但前提是索引表达式的collation(排序规则)与查询表达式的校对方式一致。以下给出两种解决方案:
- 方案1,补全SQL表达式
sql
SELECT * FROM employees WHERE CAST(data->>'$.name' AS CHAR(30)) = 'Sdd2cfb096f';
执行计划:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | employees | ref | functional_index | functional_index | 123 | const | 1 | 100 |
- 方案2,修改创建语句
sql
CREATE TABLE employees (
data JSON,
INDEX idx ((CAST(data->>"$.name" AS CHAR(30)) COLLATE utf8mb4_bin))
);
搭配的查询语句是:
sql
SELECT * FROM employees WHERE data->>'$.name' = 'Sdd2cfb096f';
以上两种方案本质上都是解决一个问题,保证查询和数据的collation相同。
唯一索引
使用 UNIQUE 会创建一个约束,要求索引中的所有值都必须是唯一的。如果添加了重复的值,会报错。如果在 UNIQUE 索引中为列指定前缀值,则列值必须在前缀长度范围内唯一。
注意,UNIQUE 索引列可以包含NULL值,并且允许有多个NULL值存在。
如果表的 PRIMARY KEY 或 UNIQUE NOT NULL 列由整数类型的单列组成,则可以在 SELECT 语句中使用 _rowid
来替代索引列字段。
- 如果 PRIMARY KEY 由单个整数列组成,则
_rowid
指向 PRIMARY KEY 列。如果存在 PRIMARY KEY,但它不是由单个整数列组成,则不能使用_rowid
。
举个例子:
sql
create table t_uniq (
id int unsigned not null primary key auto_increment,
name varchar(20) null
);
-- 填充3条数据,主键自增
insert into t_uniq (name) values('zhang'), ('qiang'), ('lisi');
如果想查询id值是1的数据,可以这样:
sql
-- 两个SQL的执行结果都是相同的
select * from t_uniq where id = 1;
select * from t_uniq where _rowid = 1;
- 否则,如果第一个 UNIQUE NOT NULL 索引包含一个整数列,
_rowid
将指向该索引中的列。如果第一个 UNIQUE NOT NULL 索引不包含单整数列,则不能使用 _rowid。
举个栗子:
perl
create table t_uniq
(
id int unsigned ,
name varchar(20) not null ,
unique index uniq(name)
);
insert into t_uniq (name) values('zhang'), ('qiang'), ('lisi');
同样的三条数据,但是因为第一个非空唯一索引列不是整数列,所以没办法使用_rowid
来代替name查询。
全文索引
仅 InnoDB 和 MyISAM 表支持 FULLTEXT 索引,且只能包含 CHAR、VARCHAR 和 TEXT 列。索引只能针对整个列,不支持列前缀索引,如果指定了前缀长度,则会被忽略。
多值索引
从 MySQL 8.0.17 开始,InnoDB 支持多值索引。多值索引是在存储数组值的列上定义的二级索引。普通索引的每条数据都有一条索引记录(1:1)。多值索引可以为一条数据设置多条索引记录(1:N)。
多值索引用于 JSON 数组的索引。例如,在以下 JSON 文档中的zipcode数组上定义的多值索引会为每个邮政编码创建一条索引记录,每条索引记录都会引用相同的数据记录。
json
{
"user":"Bob",
"user_id":31,
"zipcode":[94477,94536]
}
创建多值索引
可以使用 CREATE TABLE、ALTER TABLE 或 CREATE INDEX 语句中创建多值索引。搭配使用 CAST(... AS ... ARRAY),将 JSON 数组中的同类型标量值转换为 SQL 数据类型数组。然后,用 SQL 数据类型数组中的值透明地生成一个虚拟列;最后,在虚拟列上创建一个函数索引,举个栗子:
- 建表定义索引
sql
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)) )
);
- 单独增加索引
sql
ALTER TABLE customers ADD INDEX zips( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
sql
CREATE INDEX zips ON customers ( (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
同样多值索引也可以作为联合索引的一部分来定义,但是整个联合索引中,只能有一个多值索引部分。
sql
ALTER TABLE customers ADD INDEX comp(modified, (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)) );
空间索引
MyISAM、InnoDB、NDB 和 ARCHIVE 存储引擎支持 POINT 和 GEOMETRY 空间列。不过,不同引擎对空间列索引的支持有所不同。空间列上的空间和非空间索引可根据以下规则使用。
空间列上的空间索引具有这些特点:
- 仅适用于 InnoDB 和 MyISAM 表。为其他存储引擎指定 SPATIAL INDEX 会报错。
- 从 MySQL 8.0.12 开始,空间列上的索引必须是 SPATIAL 索引。因此,在空间列上创建索引时,SPATIAL 关键字是可选的,但却是隐含的。
- 仅适用于单个空间列。不能给多个空间列创建空间索引。
- 索引列必须为 NOT NULL。
- 不支持前缀索引。
- 不允许用于主键或唯一索引。
空间列上的非空间索引(使用 INDEX、UNIQUE 或 PRIMARY KEY 创建)具有这些特征:
- 除 ARCHIVE 外,允许用于任何支持空间列的存储引擎。
- 除非是主键索引,否则列可以为空。
- 非 SPATIAL 索引的索引类型取决于存储引擎。目前使用的是 B 树。
- 仅适用于 InnoDB、MyISAM 和 MEMORY 表中可以有 NULL 值的列。
索引选项
index_option 值可以是以下任何一种:
- KEY_BLOCK_SIZE [=]
value
对于 MyISAM 表,KEY_BLOCK_SIZE 可以指定索引键块的大小(以字节为单位)。该值被视为一个提示;如果需要,可以使用不同的大小。为单个索引定义指定的 KEY_BLOCK_SIZE 值会覆盖表级 KEY_BLOCK_SIZE 值。 index_type
某些存储引擎支持在创建索引时指定索引类型。例如:
sql
CREATE TABLE lookup (id INT) ENGINE = MEMORY;
CREATE INDEX id_index ON lookup (id) USING BTREE;
常见存储引擎支持的索引类型:
引擎 | 类型 |
---|---|
InnoDB | BTREE,RTREE |
MyISAM | BTREE |
MEMORY | HASH,BTREE |
NDB | HASH,BTREE |
index_type 子句不能用于 FULLTEX INDEX 或(MySQL 8.0.12 之前的)SPATIAL INDEX 。 全文索引的实现取决于存储引擎。空间索引以 R 树索引的形式实现。
下表显示了支持 index_type 选项的存储引擎的索引特性:
- InnoDB
索引 | 索引数据类型 | 可存储空值 | 可存储多个空值 | IS NULL 扫描类型 | IS NOT NULL 扫描类型 |
---|---|---|---|---|---|
主键索引 | BTREE | No | No | ||
唯一索引 | BTREE | Yes | Yes | Index | Index |
普通索引 | BTREE | Yes | Yes | Index | Index |
全文索引 | Yes | Yes | Table | Table | |
空间索引 | RTREE | No | No |
- MyISAM
索引 | 索引数据类型 | 可存储空值 | 可存储多个空值 | IS NULL 扫描类型 | IS NOT NULL 扫描类型 |
---|---|---|---|---|---|
主键索引 | BTREE | No | No | ||
唯一索引 | BTREE | Yes | Yes | Index | Index |
普通索引 | BTREE | Yes | Yes | Index | Index |
全文索引 | Yes | Yes | Table | Table | |
空间索引 | No | No |
- MEMORY
索引 | 索引数据类型 | 可存储空值 | 可存储多个空值 | IS NULL 扫描类型 | IS NOT NULL 扫描类型 |
---|---|---|---|---|---|
主键索引 | BTREE,HASH | No | No | ||
唯一索引 | BTREE,HASH | Yes | Yes | Index | Index |
普通索引 | BTREE,HASH | Yes | Yes | Index | Index |
- NDB
索引 | 索引数据类型 | 可存储空值 | 可存储多个空值 | IS NULL 扫描类型 | IS NOT NULL 扫描类型 |
---|---|---|---|---|---|
主键索引 | BTREE,HASH | No | No | Index,Table | Index,Table |
唯一索引 | BTREE,HASH | Yes | Yes | Index,Table | Index,Table |
普通索引 | BTREE,HASH | Yes | Yes | Index,Table | Index,Table |
-
WITH PARSER
parser_name
该选项只能用于 FULLTEXT 索引。如果全文索引和搜索操作需要特殊处理,该选项会为索引关联一个解析器插件。InnoDB 和 MyISAM 支持全文分析器插件。
-
COMMENT 'string'
COMMENT 除了可以为索引设置注释,最多支持 1024 个字符;还可以为单个索引配置索引页的 MERGE_THRESHOLD。
sql
CREATE INDEX id_index ON t1 (id) COMMENT 'MERGE_THRESHOLD=40';
如果在删除或更新数据缩短记录时,索引页的页满百分比低于 MERGE_THRESHOLD 值,InnoDB 会尝试将当前索引页与相邻的索引页合并。默认的 MERGE_THRESHOLD 值是 50,最小值是1,最大值是50。
-
VISIBLE
,INVISIBLE
指定索引的可见性。索引默认是可见的。优化程序不会使用不可见索引。指定索引可见性适用于主键(显式或隐式)以外的索引。 -
ENGINE_ATTRIBUTE
,SECONDARY_ENGINE_ATTRIBUTE
从 MySQL 8.0.21 起,可以用于指定主存储引擎和辅助存储引擎的索引属性。这些选项保留供将来使用。
这两个选项后面拼接的值是包含有效 JSON 文档的字面字符串或空字符串(''),举个栗子:
sql
CREATE INDEX i1 ON t1 (c1) ENGINE_ATTRIBUTE='{"key":"value"}';
ENGINE_ATTRIBUTE 和 SECONDARY_ENGINE_ATTRIBUTE 值可以重复,不会出错。在这种情况下,将使用最后指定的值。服务器不会检查值,也不会在更改表的存储引擎时清除这些值。
ALGORITHM
,LOCK
可以使用 ALGORITHM 和 LOCK 子句来设置表的复制方式,以及在修改表的索引时读写表的并发程度。