数据库组成索引和约束
14.1 索引简介
数据库中的索引是一个排好序的数据结构,实际上索引记录了添加索引的列值与数据表中每行记录之间的一一对应关系。
举个通俗易懂的例子,索引就好比是一本书的目录,如果书籍没有编排目录,那么想要找到书籍中的某个知识点时,就只能逐页查看是否有自己想要的内容,这样就会花费大量的时间。如果为书籍编排好目录,那么只需要查看书籍的目录来定位某个知识点的页码,随后直接翻阅书籍中指定页码的内容即可,极大地节省了查找书籍内容的时间。数据库中的索引也是同样的道理。
14.1.1 MySQL遍历表的方式
MySQL通常以两种方式遍历数据表中的数据,分别是顺序遍历和索引遍历。
1.顺序遍历
从数据表中的第一行数据开始,顺序扫描数据表中所有的数据,直到在数据表中找到匹配查询条件的目标数据。使用这种方式查询数据,数据量较小时无明显的性能问题。随着数据量越来越大,查询性能越来越低。当数据表中的数据达到百万级别甚至千万级别时,使用顺序遍历的方式查询数据,将会遍历数据表中的所有数据,花费的时间往往是不能容忍的。
2.索引遍历
通过遍历索引找到索引后,根据索引直接定位到数据表中的记录行。使用索引遍历的前提就是需要在数据表中建立相应的索引,查询数据时,根据列上的索引定位数据记录行,能极大地提高数据查询的性能。
14.1.2 索引的优点与缺点
MySQL中利用索引查询数据比没有使用索引查询数据有着明显的优势,但是索引也并不是没有缺点,本节就将MySQL中索引的优点与缺点总结如下。
1.优点
·所有的字段类型都可以添加索引。
·可以为数据表中的一列或多列添加索引。
·能够极大地提高数据的查询性能。
·能够提高数据分组与排序的性能。
2.缺点
·索引本身需要占用一定的存储空间,如果大量地使用索引,则索引文件会占用大量的磁盘空间。
·索引的创建与维护需要耗费一定的时间,随着数据量的不断增长,耗费的时间会越来越长。
·对数据表中的数据进行增加、删除和修改操作时,MySQL内部需要对索引进行动态维护,这也会消耗一定的维护时间。
14.1.3 索引的创建原则
在MySQL数据库中使用索引,虽然能够提高数据的查询性能,但是创建的索引并不是越多越好,而需要遵循一定的设计原则。
1.尽量使用小的数据类型的列创建索引
数据类型越小,所占用的存储空间越小,不仅能够节省系统的存储空间,而且处理效率也会更高。例如,同样是整数类型,能够使用TINYINT类型时,就尽量不要使用INT类型。
2.尽量使用简单的数据类型的列创建索引
处理简单的数据类型比复杂的数据类型,系统开销小,因为数据类型越复杂,执行数据的比较操作时采取的比较操作也就越复杂。例如,INT类型与VARCHAR类型,INT类型的数据互相比较时,使用比较运算符直接进行比较即可;而VARCHAR类型的数据互相比较时,需要将每个字符转化成对应的ANSI码,再进行比较。
3.尽量不要在NULL值字段上创建索引
在NULL值字段上创建索引,会使索引、索引的统计信息和比较运算更加复杂。因此在创建数据表时,尽量不要使字段的默认值为NULL,将字段设置为NOT NULL,并赋予默认值。
14.2 索引的使用场景
在MySQL中,对于索引的使用有一定的适用场景。同样,并不是每个场景都适合使用索引。本节简单总结一下适合创建索引的场景和不适合创建索引的场景。
14.2.1 适合创建索引的场景
在MySQL的实际应用中,有一些使用场景适合在数据表中创建索引,总结如下:
·必须为数据表中的主键和外键添加索引。
·数据表中的数据达到一定量级时,应当为数据表适当添加索引。
·与其他表进行关联的字段,并且经常进行关联查询时,应当为连接字段创建索引。
·作为WHERE子句的条件判断字段,并且经常用来进行相等比较操作的字段,应当添加索引。
·作为ORDER BY语句的字段,并且经常用来执行排序操作的字段,应当添加索引。
·作为搜索一定范围内的字段,并且经常用来执行查询操作,应当添加索引。
注意:这里只是列举了几个适合为字段创建索引的典型场景,其他适合为字段创建索引的场景读者可自行总结,不再赘述。
14.2.2 不适合创建索引的场景
MySQL中同样存在一些场景是不适合创建索引的,总结如下:
(1)在查询数据时很少使用的列或字段不适合创建索引。
(2)某个字段包含的数据很少,如标识用户性别的字段,不适合创建索引。
(3)大数据类型的字段,如定义为TEXT、BLOB和BIT等数据类型的字段,不适合创建索引。
(4)当在数据表中修改数据的性能远大于查询数据的性能时,不适合创建索引。
(5)查询数据时不会作为WHERE条件中的字段,并且不会作为ORDER BY语句和GROUP BY语句的字段,不适合创建索引。
注意:这里只列举了几个不适合创建索引的典型场景,其他不适合创建索引的场景,读者可自行总结,不再赘述。
14.3 创建数据表时创建索引
MySQL支持在创建数据时创建索引,本节简单介绍一下MySQL中如何在创建数据的同时为字段创建索引。
14.3.1 语法格式
创建数据表时为字段创建索引的语法格式如下:
CREATE TABLE table_name
column_name1 data_type1 [, column_name2 data_type2, …, column_namen data_typen]
[PRIMARY | UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY]
[index_name] (column_name [length])
[ASC | DESC]
语法格式说明如下:
·CREATE TABLE:创建数据表语句。
·table_name:数据表名称。
·column_name1 data_type1 [,column_name2 data_type2,…,column_namen data_typen]:创建数据表时定义的字段列表。
·[PRIMARY | UNIQUE | FULLTEXT | SPATIAL]:索引的类型,分别表示唯一索引、全文索引和空间索引,创建数据表时,索引类型可以省略。
·[INDEX | KEY]:作用基本相同,指定在数据表中创建索引。
·[index_name]:创建的索引名称,名称可以省略。
·column_name:需要创建的索引列,可以是数据表中的单个列,也可以是数据表中的多个列。
·length:创建索引时,为索引指定的长度,参数可以省略。需要注意的是,只有字符串类型的字段才能为索引指定长度。
·[ASC | DESC]:指定以升序或者降序的方式来存储索引值,参数可省略。
14.3.2 创建普通索引
普通索引是所有索引类型中最基本的索引类型,没有唯一性等限制,能够加快数据的检索效率。
例如,创建名称为t1的数据表,t1数据表中包含id、t_name、t_birthday、t_department_id和t_create_time等字段,为t_department_id创建普通索引。
mysql> CREATE TABLE t1 (
-> id INT NOT NULL,
-> t_name VARCHAR(30) NOT NULL DEFAULT '',
-> t_birthday DATE,
-> t_department_id INT NOT NULL DEFAULT 0,
-> t_create_time DATETIME,
-> INDEX department_id_index (t_department_id)
-> );
Query OK, 0 rows affected (0.20 sec)
SQL语句执行成功。创建索引后使用SHOW CREATE TABLE语句查看t1数据表的表结构。
mysql> SHOW CREATE TABLE t1 \G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int(11) NOT NULL,
`t_name` varchar(30) NOT NULL DEFAULT '',
`t_birthday` date DEFAULT NULL,
`t_department_id` int(11) NOT NULL DEFAULT '0',
`t_create_time` datetime DEFAULT NULL,
KEY `department_id_index` (`t_department_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.16 sec)
可以看到,为t_department_id字段创建了一个名为department_id_index的索引。
当创建索引未指定索引名称时,MySQL默认会以创建索引的字段名称来命名索引。例如,创建数据表t2,t2数据表的字段与t1数据表完全相同,在为字段t_department_id创建索引时,不指定索引的名称。
mysql> CREATE TABLE t2 (
-> id INT NOT NULL,
-> t_name VARCHAR(30) NOT NULL DEFAULT '',
-> t_birthday DATE,
-> t_department_id INT NOT NULL DEFAULT 0,
-> t_create_time DATETIME,
-> INDEX (t_department_id)
-> );
Query OK, 0 rows affected (0.17 sec)
SQL语句执行成功,查看t2数据表的表结构信息。
mysql> SHOW CREATE TABLE t2 \G
*************************** 1. row ***************************
Table: t2
Create Table: CREATE TABLE `t2` (
`id` int(11) NOT NULL,
`t_name` varchar(30) NOT NULL DEFAULT '',
`t_birthday` date DEFAULT NULL,
`t_department_id` int(11) NOT NULL DEFAULT '0',
`t_create_time` datetime DEFAULT NULL,
KEY `t_department_id` (`t_department_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
当没有为索引指定名称时,会使用字段的名称来命名索引。
14.3.3 创建唯一索引
创建唯一索引的列值必须唯一,但是允许值为空。如果创建的唯一索引中包含多个字段,也就是复合索引,则索引中包含的多个字段的值的组合必须唯一。
例如,创建名称为t3的数据表,并为其中的t_id_card字段创建唯一索引。
mysql> CREATE TABLE t3 (
-> id INT NOT NULL,
-> t_name VARCHAR(30) NOT NULL DEFAULT '',
-> t_id_card VARCHAR(20),
-> UNIQUE INDEX id_card_index (t_id_card)
-> );
Query OK, 0 rows affected (0.16 sec)
SQL语句执行成功,查看t3数据表的表结构信息。
mysql> SHOW CREATE TABLE t3 \G
*************************** 1. row ***************************
Table: t3
Create Table: CREATE TABLE `t3` (
`id` int(11) NOT NULL,
`t_name` varchar(30) NOT NULL DEFAULT '',
`t_id_card` varchar(20) DEFAULT NULL,
UNIQUE KEY `id_card_index` (`t_id_card`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
成功为t3数据表的t_id_card字段创建了一个名为id_card_index的唯一索引。
14.3.4 创建主键索引
主键索引是特殊类型的唯一索引,与唯一索引不同的是,主键索引不仅具有唯一性,而且不能为空,而唯一索引中的列的数据可能为空。
例如,创建数据表t4,并为t4数据表中的id字段创建主键索引。
mysql> CREATE TABLE t4 (
-> id INT NOT NULL PRIMARY KEY,
-> t_name VARCHAR(30) NOT NULL DEFAULT ''
-> );
Query OK, 0 rows affected (0.01 sec)
也可以使用如下方式创建t4数据表。
mysql> CREATE TABLE t4 (
-> id INT NOT NULL,
-> t_name VARCHAR(30) NOT NULL DEFAULT '',
-> PRIMARY KEY(id)
-> );
Query OK, 0 rows affected (0.01 sec)
SQL语句执行成功,查看t4数据表的表结构信息。
mysql> SHOW CREATE TABLE t4 \G
*************************** 1. row ***************************
Table: t4
Create Table: CREATE TABLE `t4` (
`id` int(11) NOT NULL,
`t_name` varchar(30) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
成功为id字段添加了主键索引。
14.3.5 创建单列索引
单列索引表示在创建的索引中,只包含数据表中的单个字段或列。MySQL中,支持在一张数据表中创建多个单列索引。
例如,创建名称为t5的数据表,并为id字段创建单列索引。
mysql> CREATE TABLE t5 (
-> id INT NOT NULL,
-> t_name VARCHAR(30) NOT NULL DEFAULT '',
-> INDEX id_index(id)
-> );
Query OK, 0 rows affected (0.03 sec)
SQL语句执行成功,查看t5数据表的表结构信息。
mysql> SHOW CREATE TABLE t5 \G
*************************** 1. row ***************************
Table: t5
Create Table: CREATE TABLE `t5` (
`id` int(11) NOT NULL,
`t_name` varchar(30) NOT NULL DEFAULT '',
KEY `id_index` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
已经为id字段创建了索引。
14.3.6 创建组合索引
组合索引表示在创建的索引中,包含数据表中的多个字段或列。MySQL中,同样支持在一张数据表中创建多个组合索引。在使用组合索引查询数据时,MySQL支持最左匹配原则。
例如,创建数据表t6,并为数据表中的t_no、t_name和t_department_id字段创建复合索引。
mysql> CREATE TABLE t6 (
-> id INT NOT NULL,
-> t_no VARCHAR(32) NOT NULL DEFAULT '',
-> t_name VARCHAR(30) NOT NULL DEFAULT '',
-> t_department_id INT NOT NULL DEFAULT 0,
-> INDEX no_name_department_index(t_no, t_name, t_department_id)
-> );
Query OK, 0 rows affected (0.12 sec)
SQL语句执行成功,查看t6数据表的表结构信息。
mysql> SHOW CREATE TABLE t6 \G
*************************** 1. row ***************************
Table: t6
Create Table: CREATE TABLE `t6` (
`id` int(11) NOT NULL,
`t_no` varchar(32) NOT NULL DEFAULT '',
`t_name` varchar(30) NOT NULL DEFAULT '',
`t_department_id` int(11) NOT NULL DEFAULT '0',
KEY `no_name_department_index` (`t_no`,`t_name`,`t_department_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
结果显示成功为t6数据表中的t_no、t_name和t_department_id字段创建了名称为no_name_department_index的复合索引。
名称为no_name_department_index的复合索引在进行存储时,是按照t_no/t_name/t_department_id的顺序进行存放的。根据索引的最左匹配原则,当在查询数据时,使用(t_no)、(t_no,t_name)和(t_no,t_name,t_department_id)中的一种进行查询时,MySQL会使用索引。当使用(t_name)、(t_department_id)和(t_name,t_department_id)查询数据时,MySQL不会使用索引。
下面使用EXPALIN查看t6数据表中索引的使用情况。
(1)使用t_no字段查询数据。
mysql> EXPLAIN SELECT * FROM t6 WHERE t_no = '001' \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t6
partitions: NULL
type: ref
possible_keys: no_name_department_index
key: no_name_department_index
key_len: 130
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
MySQL使用索引查询数据。
(2)使用t_no与t_name字段查询数据。
mysql> EXPLAIN SELECT * FROM t6 WHERE t_no = '001' AND t_name = 'binghe' \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t6
partitions: NULL
type: ref
possible_keys: no_name_department_index
key: no_name_department_index
key_len: 252
ref: const,const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
MySQL使用索引查询数据。
(3)使用t_no,t_name,t_department_id字段查询数据。
mysql> EXPLAIN SELECT * FROM t6 WHERE t_no = '001' AND t_name = 'binghe' AND t_department_id=1 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t6
partitions: NULL
type: ref
possible_keys: no_name_department_index
key: no_name_department_index
key_len: 256
ref: const,const,const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
MySQL查询数据时会使用索引。
(4)使用t_name字段查询数据。
mysql> EXPLAIN SELECT * FROM t6 WHERE t_name = 'binghe' \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t6
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
MySQL并没有使用索引查询数据。
(5)使用t_department_id字段查询数据。
mysql> EXPLAIN SELECT * FROM t6 WHERE t_department_id=1 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t6
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
MySQL没有使用索引查询数据。
(6)使用t_name与t_department_id字段查询数据。
mysql> EXPLAIN SELECT * FROM t6 WHERE t_name = 'binghe' AND t_department_id=1 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t6
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
MySQL查询数据时并没有使用索引。
注意:关于EXPLAIN的具体使用和说明,会在后续的MySQL优化章节详细阐述,这里不再赘述。
14.3.7 创建全文索引
创建全文索引时,对列的数据类型有一定的限制,只能为定义为CHAR、VARCHAR和TEXT数据类型的列创建全文索引,全文索引不支持对列的局部进行索引。
例如,创建数据表t7,并将字段t_info设置为全文索引。
mysql> CREATE TABLE t7 (
-> id INT NOT NULL,
-> t_name VARCHAR(30) NOT NULL DEFAULT '',
-> t_info VARCHAR(200),
-> FULLTEXT INDEX info_index (t_info)
-> );
Query OK, 0 rows affected (0.28 sec)
查看t7数据表的表结构信息。
mysql> SHOW CREATE TABLE t7 \G
*************************** 1. row ***************************
Table: t7
Create Table: CREATE TABLE `t7` (
`id` int(11) NOT NULL,
`t_name` varchar(30) NOT NULL DEFAULT '',
`t_info` varchar(200) DEFAULT NULL,
FULLTEXT KEY `info_index` (`t_info`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.01 sec)
可以看出,已经为t7数据表的t_info字段创建了名称为info_index的全文索引。
注意:在MySQL 5.7之前的版本中,只有MyISAM存储类型的数据表支持全文索引。在MySQL 5.7的部分版本和MySQL 8.x版本中,InnoDB存储引擎也支持创建全文索引。
使用EXPLAIN查看索引的使用情况。
mysql> EXPLAIN SELECT * FROM t7 WHERE MATCH (t_info) AGAINST ('abc') \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t7
partitions: NULL
type: fulltext
possible_keys: info_index
key: info_index
key_len: 0
ref: const
rows: 1
filtered: 100.00
Extra: Using where; Ft_hints: sorted
1 row in set, 1 warning (0.00 sec)
查询数据时,MySQL使用了全文索引。
14.3.8 创建空间索引
MySQL中支持在GEOMETRY数据类型的字段上创建空间索引。例如,创建名称为t8的数据表,并为t8数据表中的t_location字段创建空间索引。
mysql> CREATE TABLE t8 (
-> id INT NOT NULL,
-> t_location GEOMETRY NOT NULL,
-> SPATIAL INDEX geo_index(t_location)
-> );
Query OK, 0 rows affected, 1 warning (0.01 sec)
查看t8数据表的表结构信息。
mysql> SHOW CREATE TABLE t8 \G
*************************** 1. row ***************************
Table: t8
Create Table: CREATE TABLE `t8` (
`id` int(11) NOT NULL,
`t_location` geometry NOT NULL,
SPATIAL KEY `geo_index` (`t_location`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
已经成功为t8数据表的t_location字段创建了名称为geo_index的空间索引。
14.4 为已有数据表添加索引
MySQL支持为已经存在的数据表中的字段创建索引,可以使用ALTER TABLE语句和CREATE INDEX语句为表中的字段创建索引。
在正式介绍如何为已有数据表添加索引前,先创建名称为tb_alter和tb_create的数据表,作为本节的测试数据表。其中,tb_alter数据表用于测试使用ALTER TABLE语句创建索引,tb_create数据表用于测试使用CREATE INDEX语句创建索引,两张表的建表语句完全相同。tb_alter数据表的创建语句如下:
mysql> CREATE TABLE tb_alter (
-> id int(11) NOT NULL,
-> t_category_id int(11) DEFAULT '0',
-> t_category varchar(30) DEFAULT '',
-> t_name varchar(50) DEFAULT '',
-> t_price decimal(10,2) DEFAULT '0.00',
-> t_stock int(11) DEFAULT '0',
-> t_upper_time datetime DEFAULT NULL,
-> t_location geometry NOT NULL
-> );
Query OK, 0 rows affected, 3 warnings (0.02 sec)
本节将分别对tb_alter和tb_create数据表使用ALTER TABLE语句和CREATE INDEX语句创建相同的索引,使读者能够更加清晰地了解两种创建索引的SQL语句之间的差异。
14.4.1 语法格式
(1)ALTER TABLE语句的语法格式如下:
ALTER TABLE table_name
ADD [PRIMARY | UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY]
[index_name] (column_name [length])
[ASC | DESC]
(2)CREATE INDEX语句的语法格式如下:
CREATE [UNIQUE | FULLTEXT | SPATIAL]
INDEX index_name
ON table_name
(column_name [length])
[ASC | DESC]
14.4.2 创建普通索引
1.使用CREATE TABLE语句创建普通索引
例如,为tb_alter数据表的t_category_id字段创建普通索引。
mysql> ALTER TABLE tb_alter ADD INDEX category_id_index (t_category_id);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,查看tb_alter数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_alter \G
*************************** 1. row ***************************
Table: tb_alter
Create Table: CREATE TABLE `tb_alter` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
KEY `category_id_index` (`t_category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
成功为t_category_id字段创建了名称为category_id_index的普通索引。
2.使用CREATE INDEX语句创建普通索引
例如,为tb_create数据表的t_category_id字段创建普通索引。
mysql> CREATE INDEX category_id_index ON tb_create (t_category_id);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,查看tb_create数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_create \G
*************************** 1. row ***************************
Table: tb_create
Create Table: CREATE TABLE `tb_create` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
KEY `category_id_index` (`t_category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
成功为tb_create数据表的t_category_id字段创建了索引。
14.4.3 创建唯一索引
1.使用ALTER TABLE语句创建唯一索引
例如,为tb_alter数据表的t_category字段创建唯一索引。
mysql> ALTER TABLE tb_alter ADD UNIQUE INDEX category_index(t_category);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,查看tb_alter数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_alter \G
*************************** 1. row ***************************
Table: tb_alter
Create Table: CREATE TABLE `tb_alter` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
UNIQUE KEY `category_index` (`t_category`),
KEY `category_id_index` (`t_category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
已经成功为tb_alter数据表的t_category字段创建了唯一索引。
2.使用CREATE INDEX语句创建索引
例如,为tb_create数据表的t_category字段创建唯一索引。
mysql> CREATE UNIQUE INDEX category_index ON tb_create (t_category_id);
Query OK, 0 rows affected (0.37 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,查看tb_create数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_create \G
*************************** 1. row ***************************
Table: tb_create
Create Table: CREATE TABLE `tb_create` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
UNIQUE KEY `category_index` (`t_category_id`),
KEY `category_id_index` (`t_category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
为tb_create数据表的t_category字段创建了唯一索引。
14.4.4 创建主键索引
例如,为tb_alter数据表的id字段添加主键索引。
mysql> ALTER TABLE tb_alter ADD PRIMARY KEY (id);
Query OK, 0 rows affected (0.23 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,查看tb_alter数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_alter \G
*************************** 1. row ***************************
Table: tb_alter
Create Table: CREATE TABLE `tb_alter` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `category_index` (`t_category`),
KEY `category_id_index` (`t_category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
成功为tb_alter数据表的id字段添加了主键索引。
注意:MySQL不支持使用CREATE INDEX语句创建主键索引。
14.4.5 创建单列索引
1.使用ALTER TABLE语句创建单列索引
例如,为tb_alter数据表的t_name字段创建单例索引。
mysql> ALTER TABLE tb_alter ADD INDEX name_index (t_name);
Query OK, 0 rows affected (0.11 sec)
Records: 0 Duplicates: 0 Warnings: 0
使用SHOW CREATE TABLE语句查看tb_alter数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_alter \G
*************************** 1. row ***************************
Table: tb_alter
Create Table: CREATE TABLE `tb_alter` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `category_index` (`t_category`),
KEY `category_id_index` (`t_category_id`),
KEY `name_index` (`t_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
可以看出,已经成功为tb_alter数据表的t_name字段创建了名称为name_index的单列索引。
2.使用CREATE INDEX语句创建单列索引
例如,为tb_create数据表的t_name字段创建单列索引。
mysql> CREATE INDEX name_index ON tb_create(t_name);
Query OK, 0 rows affected (0.09 sec)
Records: 0 Duplicates: 0 Warnings: 0
查看tb_create数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_create \G
*************************** 1. row ***************************
Table: tb_create
Create Table: CREATE TABLE `tb_create` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
UNIQUE KEY `category_index` (`t_category_id`),
KEY `category_id_index` (`t_category_id`),
KEY `name_index` (`t_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
可以看出,已经成功为tb_create数据表的t_name字段添加了名称为name_index的索引。
14.4.6 创建组合索引
1.使用ALTER TABLE语句创建组合索引
例如,为tb_alter数据表的t_category和t_name字段创建组合索引。
mysql> ALTER TABLE tb_alter ADD INDEX category_name(t_category, t_name);
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,查看tb_alter数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_alter \G
*************************** 1. row ***************************
Table: tb_alter
Create Table: CREATE TABLE `tb_alter` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `category_index` (`t_category`),
KEY `category_id_index` (`t_category_id`),
KEY `name_index` (`t_name`),
KEY `category_name` (`t_category`,`t_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
可以看出,已经成功为tb_alter数据表的t_category和t_name字段创建了组合索引。
2.使用CREATE INDEX语句创建组合索引
例如,为tb_create数据表的t_category和t_name字段创建组合索引。
mysql> CREATE INDEX category_name ON tb_create (t_category, t_name);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
查看tb_create数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_create \G
*************************** 1. row ***************************
Table: tb_create
Create Table: CREATE TABLE `tb_create` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
UNIQUE KEY `category_index` (`t_category_id`),
KEY `category_id_index` (`t_category_id`),
KEY `name_index` (`t_name`),
KEY `category_name` (`t_category`,`t_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
可以看到,已经成功为tb_create数据表的t_category和t_name字段创建了组合索引。
14.4.7 创建全文索引
1.使用ALTER TABLE语句创建全文索引
例如,为tb_alter数据表的t_name字段创建全文索引。
mysql> ALTER TABLE tb_alter ADD FULLTEXT INDEX name_fulltext (t_name);
Query OK, 0 rows affected, 1 warning (0.13 sec)
Records: 0 Duplicates: 0 Warnings: 1
查看tb_alter数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_alter \G
*************************** 1. row ***************************
Table: tb_alter
Create Table: CREATE TABLE `tb_alter` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `category_index` (`t_category`),
KEY `category_id_index` (`t_category_id`),
KEY `name_index` (`t_name`),
KEY `category_name` (`t_category`,`t_name`),
FULLTEXT KEY `name_fulltext` (`t_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
可以看到,已经成功为tb_alter数据表的t_name字段添加了名称为name_fulltext的全文索引。
2.使用CREATE INDEX语句创建全文索引
例如,为tb_create数据表的t_name字段创建全文索引。
mysql> CREATE INDEX name_fulltext ON tb_create (t_name);
Query OK, 0 rows affected, 1 warning (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 1
查看tb_create数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_create \G
*************************** 1. row ***************************
Table: tb_create
Create Table: CREATE TABLE `tb_create` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
UNIQUE KEY `category_index` (`t_category_id`),
KEY `category_id_index` (`t_category_id`),
KEY `name_index` (`t_name`),
KEY `category_name` (`t_category`,`t_name`),
KEY `name_fulltext` (`t_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
可以看到,已经成功为tb_create数据表的t_name字段添加了名称为name_fulltext的全文索引。
14.4.8 创建空间索引
1.使用ALTER TABLE语句创建空间索引
例如,为tb_alter数据表的t_location字段创建全文索引。
mysql> ALTER TABLE tb_alter ADD SPATIAL INDEX location_index (t_location);
Query OK, 0 rows affected, 1 warning (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 1
查看tb_alter数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_alter \G
*************************** 1. row ***************************
Table: tb_alter
Create Table: CREATE TABLE `tb_alter` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `category_index` (`t_category`),
KEY `category_id_index` (`t_category_id`),
KEY `name_index` (`t_name`),
KEY `category_name` (`t_category`,`t_name`),
SPATIAL KEY `location_index` (`t_location`),
FULLTEXT KEY `name_fulltext` (`t_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
可以看到,已经成功为tb_alter数据表的t_location字段创建了名称为location_index的空间索引。
2.使用CREATE INDEX语句创建空间索引
例如,为tb_create数据表的t_location字段创建空间索引。
mysql> CREATE SPATIAL INDEX location_index ON tb_create (t_location);
Query OK, 0 rows affected, 1 warning (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 1
查看t_create数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_create \G
*************************** 1. row ***************************
Table: tb_create
Create Table: CREATE TABLE `tb_create` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
UNIQUE KEY `category_index` (`t_category_id`),
KEY `category_id_index` (`t_category_id`),
KEY `name_index` (`t_name`),
KEY `category_name` (`t_category`,`t_name`),
KEY `name_fulltext` (`t_name`),
SPATIAL KEY `location_index` (`t_location`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
已经成功为tb_create数据表的t_location字段创建了名称为location_index的空间索引。
注意:本节中,为了演示使用ALTER TABLE语句和CREATE INDEX语句两种方式创建索引,在数据表的多个字段上分别创建了不同的索引。在实际工作中,需要根据具体的业务与表优化选项,并依据索引的创建原则和适用场景来创建索引。
14.5 删除索引
MySQL中可以使用ALTER TABLE语句和DROP INDEX语句删除索引,本节就简单介绍一下如何在MySQL中删除索引。
14.5.1 语法格式
(1)ALTER TABLE语句的语法格式如下:
ALTER TABLE table_name
DROP INDEX index_name
(2)DROP INDEX语句的语法格式如下:
DROP INDEX index_name
ON table_name
14.5.2 删除索引方式
1.使用ALTER TABLE语句删除索引
例如,删除tb_alter数据表中名称为name_index的普通索引。
mysql> ALTER TABLE tb_alter DROP INDEX name_index;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,查看tb_alter数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_alter \G
*************************** 1. row ***************************
Table: tb_alter
Create Table: CREATE TABLE `tb_alter` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `category_index` (`t_category`),
KEY `category_id_index` (`t_category_id`),
KEY `category_name` (`t_category`,`t_name`),
SPATIAL KEY `location_index` (`t_location`),
FULLTEXT KEY `name_fulltext` (`t_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
可以看到,tb_alter数据表中的名称为name_index的索引已经被成功删除。
2.使用DROP INDEX语句删除索引
例如,删除tb_create数据表中名称为name_index的普通索引。
mysql> DROP INDEX name_index ON tb_create;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,查看tb_create数据表的表结构信息。
mysql> SHOW CREATE TABLE tb_create \G
*************************** 1. row ***************************
Table: tb_create
Create Table: CREATE TABLE `tb_create` (
`id` int(11) NOT NULL,
`t_category_id` int(11) DEFAULT '0',
`t_category` varchar(30) DEFAULT '',
`t_name` varchar(50) DEFAULT '',
`t_price` decimal(10,2) DEFAULT '0.00',
`t_stock` int(11) DEFAULT '0',
`t_upper_time` datetime DEFAULT NULL,
`t_location` geometry NOT NULL,
UNIQUE KEY `category_index` (`t_category_id`),
KEY `category_id_index` (`t_category_id`),
KEY `category_name` (`t_category`,`t_name`),
KEY `name_fulltext` (`t_name`),
SPATIAL KEY `location_index` (`t_location`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
可以看到,已经成功删除了tb_create数据表中名称为name_index的索引。
注意:删除其他索引的方式与删除普通索引的方式相同,不再赘述。
在MySQL 8.x版本中,新增了隐藏索引、降序索引和函数索引的新特性,接下来就简单介绍一下MySQL 8.x版本中的隐藏索引、降序索引和函数索引。
14.6 隐藏索引
MySQL 8.x开始支持隐藏索引,隐藏索引不会被优化器使用,但是仍然需要维护。隐藏索引通常会在软删除和灰度发布的场景中使用。
14.6.1 隐藏索引概述
在MySQL 5.7版本之前,只能通过显式的方式删除索引,此时,如果发现删除索引后出现错误,又只能通过显式创建索引的方式将删除的索引创建回来。如果数据表中的数据量非常大,或者数据表本身比较大,这种操作就会消耗系统过多的资源,操作成本非常高。
从MySQL 8.x开始支持隐藏索引,只需要将待删除的索引设置为隐藏索引,使查询优化器不再使用这个索引,确认将索引设置为隐藏索引后系统不受任何响应,就可以彻底删除索引。这种通过先将索引设置为隐藏索引,再删除索引的方式就是软删除。
创建索引时,将索引设置为隐藏索引,通过修改查询优化器的开关,使隐藏索引对查询优化器可见,通过EXPLAIN对索引进行测试,确认新创建的隐藏索引有效,再将其设置为可见索引。这种方式就是灰度发布。
14.6.2 语法格式
创建隐藏索引同样可以使用ALTER TABLE和CREATE INDEX两种方式,语法格式如下:
ALTER TABLE table_name
ADD INDEX
[index_name] (column_name [length])
INVISIBLE
或者:
CREATE INDEX index_name
ON table_name (column_name [length])
INVISIBLE
可以看到,创建隐藏索引比创建普通索引多一个关键字INVISIBLE,这个关键字就决定了当前的索引为隐藏索引。
14.6.3 创建测试表
创建一张名称为invisible_index_test的数据表,数据表中只有两个INT类型的字段visible_column和invisible_column字段。
mysql> CREATE TABLE invisible_index_test (
-> visible_column INT,
-> invisible_column INT
-> );
Query OK, 0 rows affected (0.02 sec)
SQL语句执行成功,说明数据表创建成功。
14.6.4 索引操作
(1)在visible_column字段上创建普通索引。
mysql> CREATE INDEX visible_column_index ON invisible_index_test (visible_column);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
(2)在invisible_column字段上创建隐藏索引。
mysql> CREATE INDEX invisible_column_index ON invisible_index_test (invisible_column) INVISIBLE;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,说明两个索引创建成功。接下来,可以使用SHOW CREATE TABLE语句和SHOW INDEX FROM语句查看数据表中使用的索引。
(3)使用SHOW CREATE TABLE语句查看invisible_index_test数据表中的索引。
mysql> SHOW CREATE TABLE invisible_index_test \G
*************************** 1. row ***************************
Table: invisible_index_test
Create Table: CREATE TABLE `invisible_index_test` (
`visible_column` int(11) DEFAULT NULL,
`invisible_column` int(11) DEFAULT NULL,
KEY `visible_column_index` (`visible_column`),
KEY `invisible_column_index` (`invisible_column`) /*!80000 INVISIBLE */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
结果显示,invisible_index_test数据表中存在名称为visible_column_index和名称为invisible_column_index的两个索引,其中,名称为invisible_column_index的索引后面有一个标识/*!80000 INVISIBLE*/,说明名称为invisible_column_index的索引为隐藏索引。
(4)使用SHOW INDEX FROM语句查看invisible_index_test数据表中的索引。
mysql> SHOW INDEX FROM invisible_index_test \G
*************************** 1. row ***************************
Table: invisible_index_test
Non_unique: 1
Key_name: visible_column_index
Seq_in_index: 1
Column_name: visible_column
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: invisible_index_test
Non_unique: 1
Key_name: invisible_column_index
Seq_in_index: 1
Column_name: invisible_column
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: NO
Expression: NULL
2 rows in set (0.00 sec)
invisible_index_test数据表中存在名称为visible_column_index和名称为invisible_column_index的两个索引。名称为visible_column_index的索引的Visible属性为YES,说明是可见索引,也就是普通索引。名称为invisible_column_index的索引的Visible属性为NO,说明是隐藏索引。
(5)使用EXPAIN查看查询优化器对索引的使用情况。首先,查看以字段visible_column作为查询条件时的索引使用情况。
mysql> EXPLAIN SELECT * FROM invisible_index_test WHERE visible_column = 1 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: invisible_index_test
partitions: NULL
type: ref
possible_keys: visible_column_index
key: visible_column_index
key_len: 5
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.09 sec)
查询优化器在执行SQL语句时,会使用字段visible_column上创建的名称为visible_column_index的索引。
接下来,查看以字段invisible_column作为查询条件时的索引使用情况。
mysql> EXPLAIN SELECT * FROM invisible_index_test WHERE invisible_column = 1 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: invisible_index_test
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
当使用invisible_column字段作为查询条件时,查询优化器不会使用invisible_column字段上创建的名称为invisible_column_index的隐藏索引,说明隐藏索引默认对查询优化器是不可见的。
(6)使隐藏索引对查询优化器可见。在MySQL 8.x版本中,为索引提供了一种新的测试方式,可以通过查询优化器的一个开关来打开某个设置,使隐藏索引对查询优化器可见。
在MySQL命令行执行如下命令查看查询优化器的开关设置。
mysql> select @@optimizer_switch \G
*************************** 1. row ***************************
@@optimizer_switch: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_
intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,
block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=
on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_
fanout_filter=on,derived_merge=on,use_invisible_indexes=off,skip_scan=on,hash_join=on
1 row in set (0.00 sec)
在输出的结果信息中找到如下属性配置。
use_invisible_indexes=off
此属性配置值为off,说明隐藏索引默认对查询优化器不可见。
接下来,使隐藏索引对查询优化器可见,需要在MySQL命令行执行如下命令:
mysql> set session optimizer_switch="use_invisible_indexes=on";
Query OK, 0 rows affected (0.00 sec)
SQL语句执行成功,再次查看查询优化器的开关设置。
mysql> select @@optimizer_switch \G
*************************** 1. row ***************************
@@optimizer_switch: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_
intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,
block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=
on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_
fanout_filter=on,derived_merge=on,use_invisible_indexes=on,skip_scan=on,hash_join=on
1 row in set (0.00 sec)
此时,在输出结果中可以看到如下属性配置。
use_invisible_indexes=on
use_invisible_indexes属性的值为on,说明此时隐藏索引对查询优化器可见。
(7)再次使用EXPLAIN查看以字段invisible_column作为查询条件时的索引使用情况。
mysql> EXPLAIN SELECT * FROM invisible_index_test WHERE invisible_column = 1 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: invisible_index_test
partitions: NULL
type: ref
possible_keys: invisible_column_index
key: invisible_column_index
key_len: 5
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
查询优化器会使用隐藏索引来查询数据。
(8)如果需要使隐藏索引对查询优化器不可见,则只需要执行如下命令即可。
mysql> set session optimizer_switch="use_invisible_indexes=off";
Query OK, 0 rows affected (0.00 sec)
再次查看查询优化器的开关设置。
mysql> select @@optimizer_switch \G
*************************** 1. row ***************************
@@optimizer_switch: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_
intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,
block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=
on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_
fanout_filter=on,derived_merge=on,use_invisible_indexes=off,skip_scan=on,hash_join=on
1 row in set (0.00 sec)
此时,use_invisible_indexes属性的值已经被设置为“off”。
(9)可以将一个普通索引设置为隐藏索引,例如,将visible_column字段的索引修改为隐藏索引。
mysql> ALTER TABLE invisible_index_test ALTER INDEX visible_column_index INVISIBLE;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,使用SHOW INDEX FROM语句查看invisible_index_test数据表的索引。
mysql> SHOW INDEX FROM invisible_index_test \G
*************************** 1. row ***************************
Table: invisible_index_test
Non_unique: 1
Key_name: visible_column_index
Seq_in_index: 1
Column_name: visible_column
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: NO
Expression: NULL
*************************** 2. row ***************************
Table: invisible_index_test
Non_unique: 1
Key_name: invisible_column_index
Seq_in_index: 1
Column_name: invisible_column
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: NO
Expression: NULL
2 rows in set (0.00 sec)
visible_column字段的索引的Visible属性为“NO”,说明索引被修改为隐藏索引。
(10)MySQL同样支持将一个隐藏索引修改为普通索引,例如,将visible_column字段的索引修改为可见索引。
mysql> ALTER TABLE invisible_index_test ALTER INDEX visible_column_index VISIBLE;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,查看invisible_index_test数据表的索引。
mysql> SHOW INDEX FROM invisible_index_test \G
*************************** 1. row ***************************
Table: invisible_index_test
Non_unique: 1
Key_name: visible_column_index
Seq_in_index: 1
Column_name: visible_column
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: invisible_index_test
Non_unique: 1
Key_name: invisible_column_index
Seq_in_index: 1
Column_name: invisible_column
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: NO
Expression: NULL
2 rows in set (0.00 sec)
visible_column字段的索引已经被成功修改为可见索引。
注意:MySQL中不能将主键设置为隐藏索引。例如,新建数据表t。
mysql> CREATE TABLE t(
-> id INT NOT NULL
-> );
Query OK, 0 rows affected (0.01 sec)
在t数据表的id字段上创建一个隐藏索引。
mysql> ALTER TABLE t ADD PRIMARY KEY id_key(id) INVISIBLE;
ERROR 3522 (HY000): A primary key index cannot be invisible
结果显示主键不能被隐藏,也就是说,不能将主键设置为隐藏索引。
14.7 降序索引
从MySQL 4版本开始就已经支持降序索引的语法了,但是直到MySQL 8.x版本才开始真正支持降序索引。本节就对比MySQL 5.7与MySQL 8.x中对降序索引的处理来介绍MySQL 8.x中的降序索引特性。
14.7.1 降序索引概述
MySQL 8.x版本中另一项重要的新特性就是真正开始支持降序索引,但是只有MySQL的InnoDB存储引擎支持降序索引,同时,只有BTREE索引支持降序索引。另外,在MySQL 8.x版本中,不再对GROUP BY语句进行隐式排序。
14.7.2 降序索引操作
1.创建表并指定降序索引
(1)在MySQL 5.7版本上创建数据库test,并在test数据库中创建test_desc数据表,并为test_desc数据表创建组合索引idx,包含的字段为c1和c2,同时c1字段按照升序排列,c2字段按照降序排列。
mysql> CREATE DATABASE testdb;
Query OK, 1 row affected (0.00 sec)
mysql> USE testdb;
Database changed
mysql> CREATE TABLE test_desc (
-> c1 INT,
-> c2 INT,
-> INDEX idx(c1 asc, c2 desc)
-> );
Query OK, 0 rows affected (0.24 sec)
SQL语句执行成功,查看test_desc的表结构信息。
mysql> SHOW CREATE TABLE test_desc \G
*************************** 1. row ***************************
Table: test_desc
Create Table: CREATE TABLE `test_desc` (
`c1` int(11) DEFAULT NULL,
`c2` int(11) DEFAULT NULL,
KEY `idx` (`c1`,`c2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.01 sec)
结果显示,MySQL 5.7中,尽管在创建数据表时,将名称为idx的组合索引中的c1字段设置为升序,将c2字段设置为降序,但是在查看表结构信息时,c1和c2字段在组合索引中还是以默认的升序进行排列。
(2)在MySQL 8.x版本上执行MySQL 5.7上的操作,查看test_desc数据表的表结构信息如下:
mysql> SHOW CREATE TABLE test_desc \G
*************************** 1. row ***************************
Table: test_desc
Create Table: CREATE TABLE `test_desc` (
`c1` int(11) DEFAULT NULL,
`c2` int(11) DEFAULT NULL,
KEY `idx` (`c1`,`c2` DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
在MySQL 8.x版本中,组合索引中的c1字段按照升序排列,c2字段按照降序排列,说明MySQL 8.x版本开始真正支持降序索引。
2.查看索引的使用情况
(1)在MySQL 5.7版本中,使用EXPLAIN查看查询优化器对降序索引的使用情况。
mysql> EXPLAIN SELECT * FROM test_desc ORDER BY c1, c2 DESC \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: test_desc
partitions: NULL
type: index
possible_keys: NULL
key: idx
key_len: 10
ref: NULL
rows: 1
filtered: 100.00
Extra: Using index; Using filesort
1 row in set, 1 warning (0.17 sec)
从输出的结果信息可以看出,在MySQL 5.7中,按照test_desc数据表的c1字段进行升序,同时按照c2字段进行降序查询的结果为MySQL并没有使用c2字段上的降序索引。
(2)在MySQL 8.x版本中,使用EXPLAIN查看查询优化器对降序索引的使用情况。
mysql> EXPLAIN SELECT * FROM test_desc ORDER BY c1, c2 DESC \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: test_desc
partitions: NULL
type: index
possible_keys: NULL
key: idx
key_len: 10
ref: NULL
rows: 1
filtered: 100.00
Extra: Using index
1 row in set, 1 warning (0.00 sec)
在MySQL 8.x中,按照c2字段进行降序排序,使用了索引。
查询test_desc数据表中的数据时,按照c1字段进行降序,按照c2字段进行升序。
mysql> EXPLAIN SELECT * FROM test_desc ORDER BY c1 DESC, c2 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: test_desc
partitions: NULL
type: index
possible_keys: NULL
key: idx
key_len: 10
ref: NULL
rows: 1
filtered: 100.00
Extra: Backward index scan; Using index
1 row in set, 1 warning (0.00 sec)
在MySQL 8.x中按照c1字段和c2字段进行反向排序时仍然使用了索引,此时会使用索引的反向扫描。
3.对GROUP语句的处理
MySQL 8.x中不再对GROUP BY语句进行隐式排序,下面分别在MySQL 5.7和MySQL 8.x版本的命令行执行命令,为test_desc数据表插入测试数据。
mysql> INSERT INTO test_desc VALUES (1, 100), (2, 200), (3, 150), (4, 50);
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
接下来对比MySQL 5.7版本与MySQL 8.x版本对GROUP BY语句的隐式排序处理情况。
(1)在MySQL 5.7版本中,查询test_desc数据表中的数据,按照c2字段进行分组,并查询每个分组中的记录条数。
mysql> SELECT c2, COUNT(*) FROM test_desc GROUP BY c2;
+------+----------+
| c2 | COUNT(*) |
+------+----------+
| 50 | 1 |
| 100 | 1 |
| 150 | 1 |
| 200 | 1 |
+------+----------+
4 rows in set (0.40 sec)
c2数据列会按照升序进行排序,这是因为在MySQL 5.7版本中会对GROUP BY字段的数据进行隐式的排序操作。
(2)在MySQL 8.x版本中执行相同的操作。
mysql> SELECT c2, COUNT(*) FROM test_desc GROUP BY c2;
+------+----------+
| c2 | COUNT(*) |
+------+----------+
| 100 | 1 |
| 200 | 1 |
| 150 | 1 |
| 50 | 1 |
+------+----------+
4 rows in set (0.00 sec)
由结果可知,MySQL 8.x版本中并没有对c2字段的数据列进行排序操作。在MySQL 8.x版本中如果需要对c2的数据列进行排序,需要显式地使用ORDER BY语句明确指定排序规则。
mysql> SELECT c2, COUNT(*) FROM test_desc GROUP BY c2 ORDER BY c2;
+------+----------+
| c2 | COUNT(*) |
+------+----------+
| 50 | 1 |
| 100 | 1 |
| 150 | 1 |
| 200 | 1 |
+------+----------+
4 rows in set (0.00 sec)
注意:上面简单对比了MySQL 5.7与MySQL 8.x中关于降序索引的使用情况,关于其他版本中降序索引的不同点,读者可以自行总结,不再赘述。
14.8 函数索引
函数索引是MySQL 8.x版本开始支持的另一大索引特性,本节就简单探讨一下MySQL 8.x版本中的函数索引。
14.8.1 函数索引概述
从MySQL 8.0.13版本开始支持在索引中使用函数或者表达式的值,也就是在索引中可以包含函数或者表达式。函数索引中支持降序索引,同时,MySQL 8.x版本支持对JSON类型的数据添加函数索引。函数索引可以基于虚拟列功能实现。
14.8.2 函数索引操作
(1)创建数据表func_index。
mysql> CREATE TABLE func_index (
-> c1 VARCHAR(10),
-> c2 VARCHAR(10)
-> );
Query OK, 0 rows affected (0.02 sec)
SQL语句执行成功。
(2)为fun_index数据表的c1字段创建普通索引。
mysql> CREATE INDEX c1_index ON func_index (c1);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
(3)为func_index数据表的c2字段创建一个将字段值转化为大写的函数索引。
mysql> CREATE INDEX c2_index ON func_index ((UPPER(c2)));
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
SQL语句执行成功,可以看到,在创建名称为c2_index的索引时,将UPPER(c2)函数指定为了索引的内容。
(4)查看func_index数据表中的索引。
mysql> SHOW INDEX FROM func_index \G
*************************** 1. row ***************************
Table: func_index
Non_unique: 1
Key_name: c1_index
Seq_in_index: 1
Column_name: c1
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: func_index
Non_unique: 1
Key_name: c2_index
Seq_in_index: 1
Column_name: NULL
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: upper(`c2`)
2 rows in set (0.01 sec)
func_index数据表中存在名称为c1_index和c2_index的索引,并且c2_index的索引的Expression属性为upper(`c2`)。说明名称为c2_index的索引为函数索引,使用的函数表达式为UPPER()。
(5)查看c1字段的大写值是否等于某个特定值时,查询优化器对索引的使用情况。
mysql> EXPLAIN SELECT * FROM func_index WHERE UPPER(c1) = 'ABC' \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: func_index
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
结果显示优化器并没有使用索引,而是进行了全表扫描操作。
接下来查看c2字段的大写值是否等于某个特定的值时,查询优化器对索引的使用情况。
mysql> EXPLAIN SELECT * FROM func_index WHERE UPPER(c2) = 'ABC' \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: func_index
partitions: NULL
type: ref
possible_keys: c2_index
key: c2_index
key_len: 43
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
由结果可知,MySQL使用了c2字段上的函数索引。
(6)MySQL 8.x版本支持对JSON类型的数据添加函数索引。创建数据表func_json。
mysql> CREATE TABLE func_json (
-> data JSON,
-> INDEX((CAST(data->>'$.name' AS CHAR(30))))
-> );
Query OK, 0 rows affected (0.15 sec)
SQL语句执行成功,对上述SQL语句的解释如下:
·当JSON数据长度不固定时,如果直接对JSON数据进行索引,可能会超出索引长度,通常只截取JSON数据的一部分进行索引。
·CAST()类型转换函数把数据转化为CHAR(30)类型。使用方式为CAST(数据 AS 数据类型)。
·data ->> '$.name'表示JSON的运算符。
可以将func_json数据表中的索引理解为获取JSON数据中name节点的值,并将其转化为CHAR(30)类型。
查看func_json数据表中的索引。
mysql> SHOW INDEX FROM func_json \G
*************************** 1. row ***************************
Table: func_json
Non_unique: 1
Key_name: functional_index
Seq_in_index: 1
Column_name: NULL
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: cast(json_unquote(json_extract(`data`,_utf8mb4\'$.name\')) as char(30) charset utf8mb4)
1 row in set (0.01 sec)
可以看出,已经为func_json数据表的data字段成功添加了函数索引。
(7)在MySQL 8.x版本中,函数索引可以基于虚拟列进行实现。查看func_index数据表的信息。
mysql> SHOW CREATE TABLE func_index \G
*************************** 1. row ***************************
Table: func_index
Create Table: CREATE TABLE `func_index` (
`c1` varchar(10) DEFAULT NULL,
`c2` varchar(10) DEFAULT NULL,
KEY `c1_index` (`c1`),
KEY `c2_index` ((upper(`c2`)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
可以看出,在c1字段上创建了普通索引,在c2字段上创建了函数索引。
接下来,在func_index数据表中添加一个名称为c3的字段,模拟c2字段上的函数索引。
mysql> ALTER TABLE func_index ADD COLUMN
-> c3 VARCHAR(10)
-> GENERATED ALWAYS AS (UPPER(c1));
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
由执行的SQL语句可以看出,c3字段是一个计算列,c3字段的值总是使用c1字段转化为大写后的结果数据。
向func_index数据表中插入一行数据,因为c3字段是一个计算列,值总是使用c1字段转化为大写的结果数据,所以在向func_index数据表中插入数据时,不需要为c3字段插入数据。
mysql> INSERT INTO func_index(c1, c2) VALUES ('mysql', 'hello');
Query OK, 1 row affected (0.00 sec)
SQL语句执行成功,查看func_index数据表中的数据。
mysql> SELECT * FROM func_index;
+-------+-------+-------+
| c1 | c2 | c3 |
+-------+-------+-------+
| mysql | hello | MYSQL |
+-------+-------+-------+
1 row in set (0.00 sec)
结果显示,不需要为c3字段添加数据,c3字段的数据为c1字段的数据转化为大写后的结果数据。
(8)如果想在c3字段上模拟函数索引的效果,则可以在c3字段上添加索引。
mysql> CREATE INDEX c3_index ON func_index (c3);
Query OK, 0 rows affected (0.12 sec)
Records: 0 Duplicates: 0 Warnings: 0
再次查看c1字段的大写值是否等于某个特定的值时,查询优化器对索引的使用情况。
mysql> EXPLAIN SELECT * FROM func_index WHERE UPPER(c1) = 'ABC' \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: func_index
partitions: NULL
type: ref
possible_keys: c3_index
key: c3_index
key_len: 43
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
此时MySQL使用了c1字段上的索引,说明MySQL中的函数索引可以通过虚拟列进行模拟实现。
注意:这里在简单介绍函数索引时,在索引中使用了UPPER()函数进行说明,读者也可以使用MySQL中支持的其他函数进行实现,不再赘述。
- 点赞
- 收藏
- 关注作者
评论(0)