索引的创建和设计原则

三、索引的创建与设计原则

1. 索引的声明与使用

1.1 索引的分类

MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。

  • 功能逻辑上说,索引主要有 4 种,分别是普通索引、唯一索引、主键索引、全文索引。
  • 按照物理实现方式,索引可以分为 2 种:聚簇索引和非聚簇索引。
  • 按照作用字段个数进行划分,分为 单列索引和联合索引。

1. 普通索引

在创建普通索引时,不附加任何限制条件,只是用于提高查询效率。这类索引可以创建在任何数据类型中,其值是否唯一和非空,要由字段本身的完整性约束条件决定。建立索引以后,可以通过索引进行查询。例如,在student表的字段name建立一个普通索引,查询记录时就可以根据该索引进行查询。

2. 唯一索引

使用UNIQUE参数可以设置索引为唯一索引,在创建唯一索引时,限制该索引的值必须是唯一的,但允许有空值。在一张表中可以有多个唯一索引。

例如,在表student的字段email中创建唯一性索引,那么字段email的值就必须唯一,通过唯一索引,可以快速地确定某条记录。

3. 主键索引

主键索引就是一种特殊的唯一索引,在唯一索引的基础上增加了不为空的约束,也就是NOT NULL + UNIQUE,一张表里最多只有一个主键索引。

因为这是由主键索引的物理实现方式决定的,因为数据存储在文件中只能按照一种顺序进行存储。

4. 单列索引

在表中的单个字段上创建索引。单列索引只根据该字段进行索引。单列索引可以是普通索引,也可以是唯一索引,还可以是全文索引。只要保证该索引只对应一个字段即可。一个表可以有多个单列索引。

5. 多列(组合、联合)索引

多列索引是在表的多个字段组合上创建一个索引,该索引指向创建时对应的多个字段,可以通过这几个字段进行查询,但是只有查询条件中使用了这些字段中的第一个字段时才会被使用。例如,在表中的字段id、name和gender上建立一个多列索引idx_id_name_gender,只有在查询条件中使用了字段id时该索引才会被使用。使用组合索引时遵循最左前缀法则

6. 全文索引

全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用分词技术等多种算法只能分析出文本文字中关键词的频率和重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结构。全文索引非常适合大型数据集,对于小的数据集,它的用处比较小。

使用参数FULLTEXT可以设置索引为全文索引。在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值。全文索引只能创建在CHARVARCHARTEXT类型及其系列类型的字段上,查询数据量较大的字符串类型的字段时,使用全文索引可以提高查询速度。例如,表student的字段informationTEXT类型,该字段包含了很多文字信息。在字段information上建立全文索引后,可以提高查询字段information的速度。

全文索引典型的有两种类型:自然语言的全文索引和布尔全文索引。

  • 自然语言搜索引擎将计算每一个文档对象和查询的相关度。这里,相关度是基于匹配的关键词的个数,以及关键词在文档中出现的次数,在整个索引中出现次数越少的词语,匹配时的相关度就越高。相反,非常常见的单词将不会被搜索,如果一个词语的在超过50%的记录中都出现了,那么自然语言的搜索将不会搜索这类词语。

MySQL数据库从3.23版开始支持全文索引,但MySQL5.6.4以前只有MyISAM支持,5.6.4版本以后InnoDB才支持,但是官方版本不支持中文分词,需要第三方分词插件。在5.7.6版本中,MySQL内置了ngram全文解析器,用来支持亚洲语种的分词。

随着大数据时代的到来,关系型数据库对全文索引的需求已力不从心,逐渐被solrElastaicSearch等专门的搜索引擎所替代。

7. 空间索引

使用参数SPATIAL可以设置索引为空间索引。空间索引只能建立在空间数据类型上,这样可以提高系统获取空间数据的效率。MySQL中的空间数据类型包括GEOMETRYPOINTLINESTRINGPOLYGON等。目前只有MyISAM存储引擎支持空间检索,而且索引的字段不能为空值。

1.2 创建索引

MySQL支持多种方法在单个或多个列上创建索引:在创建表的定义语句CREATE TABLE中指定索引列,使用ALTER TABLE语句在存在的表上创建索引,或者使用CREATE INDEX语句在已存在的表上添加索引。

1. 创建表的时候创建索引

隐式的方式创建索引。在声明有主键约束唯一性约束外键约束的字段上,会自动的添加相关的索引

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE dept(
dept_id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(20)
);

CREATE TABLE emp(
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(20) UNIQUE,
dept_id INT,
CONSTRAINT emp_dept_id_fk FOREIGN KEY(dept_id) REFERENCES dept(dept_id)
);

显式的创建索引,基本语法:

1
2
CREATE TABLE table_name [col_name data_type]
[UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name [length]) [ADC | DESC]
  • UNIQUEFULLTEXTSPATIAL为可选参数,分别表示 唯一索引全文索引空间索引
  • INDEXKEY为同义词,两者的作用相同,用来指定创建索引
  • index_name指定索引的名称,为可选参数,如果不指定,那么MySQL默认col_name为索引名
  • col_name为需要创建索引的字段列,该列必须从数据表中定义的多个列种选择
  • length为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度
  • ASCDESC指定升序或者降序的索引值存储。
① 创建普通索引
1
2
3
4
5
6
7
8
9
10
CREATE TABLE book(
book_id INT,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100),
COMMENT VARCHAR(100),
year_publication YEAR,
# 声明索引
INDEX idx_book_name(book_name)
);

查看索引

1
2
3
4
# 方式1
SHOW CREATE TABLE book\G;
# 方式2
SHWO INDEX FROM book;
② 创建唯一索引 (UNIQUE)
1
2
3
4
5
6
7
8
9
10
CREATE TABLE book1(
book_id INT,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100),
COMMENT VARCHAR(100),
year_publication YEAR,
# 声明索引
UNIQUE INDEX uk_idx_comment(COMMENT)
);
③ 主键索引

通过定义主键约束的方式定义主键索引

1
2
3
4
5
6
7
8
CREATE TABLE book2(
book_id INT PRIMARY KEY,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100),
COMMENT VARCHAR(100),
year_publication YEAR
);

通过删除主键约束的方式删除主键索引

1
ALTER TABLE book2 DROP PRIMARY KEY;
④ 创建组合索引
1
2
3
4
5
6
7
8
9
10
CREATE TABLE book3(
book_id INT,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100),
COMMENT VARCHAR(100),
year_publication YEAR,
# 声明联合索引
INDEX mul_id_name(book_id,book_name,info)
);
⑤ 创建全文索引

FULLTEXT全文索引可以用于全文搜索,并且只为CHARVARCHARTEXT列创建索引。索引总是对整个列进行,不支持局部(前缀)索引。

1
2
3
4
5
6
7
8
9
10
CREATE TABLE book4(
book_id INT,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100),
COMMENT VARCHAR(100),
year_publication YEAR,
# 声明全文索引
FULLTEXT INDEX futxt_idx_info(info(length))
);

不同于like方式的查询:

1
SELECT * FROM papers WHERE content LIKE '%查询字符串%'

全文索引用match+ageinst方式查询:

1
SELECT * FROM papers WHERE MATCH(title,content) AGAINST ('查询字符串');

明显的提高查询效率。

注意:

  1. 全文索引比 like + % 快 N 倍,但是可能存在精度问题;
  2. 如果需要全文索引的是大量数据,建议先添加数据,再创建索引。
⑥ 创建空间索引

空间索引创建中,要求空间类型的字段必须为非空

1
2
3
4
CREATE TABLE test(
geo GEOMETRY NOT NULL,
SPATIAL INDEX spa_idx_geo(geo)
)ENGINE=MyISAM;

2. 在已经存在的表上创建索引

1. 使用ALTER TABLE 语句创建索引

ALTER TABLE 语句创建索引的基本语法如下:

1
2
ALTER TABLE table_name ADD [UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY]
[index_name] (col_name[length],...) [ASC|DESC]
2. 使用CREATE INDEX 创建索引
1
2
CREATE [UNIQUE | FULLTEXT | SPATITAL] INDEX index_name
ON table_name (col_name[length],...) [ASC | DESC]

1.3 删除索引

MySQL中删除索引使用ALTER TABLE ... DROP 或者DROP INDEX语句。

1. 使用ALTER TABLE 删除索引

1
ALTER TABLE table_name DROP INDEX index_name;

添加AUTO_INCREMENT约束字段的唯一索引不能删除。

2. 使用DROP INDEX删除索引

1
DROP INDEX index_name ON table_name;

删除表中的列时,如果要删除的列为索引的组成部分,则该列也会从索引中删除。如果组成索引的所有列被删除,则整个索引将被删除。

2. MySQL8.0 索引新特性(待完善)

MySQL高级130P

2.1 支持降序索引

3. 索引的设计原则

为了使索引的使用效率更高,在创建索引时,必须考虑在哪些字段上创建索引和创建什么类型的索引。索引设计不合理或者缺少索引都会对数据库和应用程序的性能造成障碍。高效的索引对于获得良好的性能非常重要。

3.1 数据准备

第一步:创建数据库、创建表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE DATABASE index_demo_db;

USE index_demo_db;
#1.创建学生表和课程表
CREATE TABLE `student_info`(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`student_id` INT NOT NULL,
`name` VARCHAR(20) DEFAULT NULL,
`course_id` INT NOT NULL,
`class_id` INT(11) DEFAULT NULL,
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `course`(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`course_id` INT NOT NULL,
`course_name` VARCHAR(40) DEFAULT NULL,
PRIMARY KEY(`id`)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

第二步:创建模拟数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#函数1:创建随机产生字符串函数

DELIMITER //
CREATE FUNCTION rand_string(n INT)
RETURNS VARCHAR(255) #该函数会返回一个字符串
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END //
DELIMITER ;

SELECT @@log_bin_trust_function_creators;

SET GLOBAL log_bin_trust_function_creators = 1;


#函数2:创建随机数函数
DELIMITER //
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1)) ;
RETURN i;
END //
DELIMITER ;

第三步:创建存储过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 存储过程1:创建插入课程表存储过程
DELIMITER //
CREATE PROCEDURE insert_course( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i = i + 1; #赋值
INSERT INTO course (course_id, course_name ) VALUES (rand_num(10000,10100),rand_string(6));
UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END //
DELIMITER ;


# 存储过程2:创建插入学生信息表存储过程
DELIMITER //
CREATE PROCEDURE insert_stu( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i = i + 1; #赋值
INSERT INTO student_info (course_id, class_id ,student_id ,NAME ) VALUES (rand_num(10000,10100),rand_num(10000,10200),rand_num(1,200000),rand_string(6));
UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END //
DELIMITER ;

第四步:调用存储过程

1
2
3
4
5
6
7
8
#调用存储过程:
CALL insert_course(100);

SELECT COUNT(*) FROM course;

CALL insert_stu(1000000);

SELECT COUNT(*) FROM student_info;

3.2 哪些情况适合创建索引

1. 字段的数值有唯一性的限制

索引本身可以起到约束的作用,比如唯一索引、主键索引都是可以起到唯一性约束的,因此在我们的数据表中,如果某个字段是唯一性的,就可以直接创建唯一性索引,或者主键索引。这样可以更快速地通过该索引来确定某条记录。

2. 频繁作为where查询条件的字段

3. 经常GROUP BYORDER BY的列

4. UPDATEDELETEWHERE条件列

5. DISTINCT字段需要创建索引

6. 多表JOIN连接操作时,创建索引注意事项

7. 使用列的类型小的创建索引

8. 使用字符串前缀创建索引

9. 区分度高(散列性高)的列适合作为索引

10. 使用最频繁的列放到联合索引的左侧

11. 在多个字段都要创建索引的情况下,联合索引优于单值索引

3.3 索引失效的情况

1. 索引列不独立

索引列不独立是指 被索引的这列不能是表达式的一部分,不能是函数的参数,比如下面的这种情况

1
select id,name,age,salary from table_name where salary + 1000 = 6000;

salary 列被用户表达式的计算了,这种情况下索引就会失效,解决方式就是提前计算好条件值,不要让索引列参与表达式计算,修改后 sql 如下

1
select id,name,age,salary from table_name where salary = 5000;

2.使用了左模糊

3. 使用 or 查询部分字段没有使用索引

1
select id,name,age,salary from table_name where name ='lucs' and age >25

这种情况,可以为 name 和 age 都建立索引,否则会走全表扫描。

4. 字符串条件没有使用 ‘’

5. 不符合最左前缀原则的查询

例如有这样一个组合索引 index(a,b,c)

1
2
3
4
select * from table_name where b='1'and c='2'
select * from table_name where c='2'

// 上面这两条 SQL 都是无法走索引执行的

6. 索引字段没有添加 not null 约束

7. 隐式转换导致索引失效

1
2
3
4
select * from table_name t1 left join table_name2 t2 on t1.id=t2.tid;
// 上面这条语句里,如果 t1 表的id 类型和 t2 表的tid 类型不一致的时候,就无法
// 按索引执行了。
// 解决方式就是统一设置字段类型。