文章目录
- 第一章、索引简介
- 1.1 索引定义
- 1.2 使用索引的目的
- 1.3 B+树结构
- 第二章 常见索引概念
- 2.1 聚簇索引
- 2.2 二级索引(辅助索引、非聚簇索引)
- 2.3 比较
- 2.4 联合索引
- 2.5 总结
- 2.6 索引的代价
- 第三章 索引的分类和创建
- 3.1 索引分类
- 3.2 创建和删除索引
- 第四章 索引的设计原则
- 4.1 适合创建索引的情形和建议
- 4.2 不适合创建索引的情况
- 4.3 小结
第一章、索引简介
1.1 索引定义
官方定义:索引是帮助MySQL高效查询数据的数据结构
。
索引的本质:索引是数据结构,可以简单理解为排好序的快速查找数据结构,满足特定查找算法。这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法。
1.2 使用索引的目的
为了减少磁盘I/O的次数
,加快查询
效率。
1.3 B+树结构
- 上图中最下面一行的黄色框表示数据库表中的第一列(主键列c1),蓝色框表示第二列(c2),紫色框表示第三列(c3)。
- 查询数据涉及磁盘IO,数据页的交互,使用B+树之后,完成一次查询所需要的数据页不超过4个。
第二章 常见索引概念
按照物理实现方式,索引可以分为2种:聚簇(聚集)和非聚簇(非聚集)索引。也称非聚集索引为二级索引或辅助索引。
2.1 聚簇索引
1.定义
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式,索引即数据,数据即索引
。
聚簇表示数据行和相邻的键值存储在一起,索引的顺序就是数据在磁盘中实际的存储顺序。
上图是索引需要的顺序。我们使用主键id自增的方式向数据库添加数据时,一个数据页填满就放到下一个数据页,不会出现页分裂
的问题,而且数据的存储顺序就是索引需要的顺序,所以索引即数据,数据即索引
。
2.特点
对照B+树的图:
- 页内的记录是按照主键的大小顺序排成一个
单向链表
- 各个存放用户记录的页也是根据页中记录的主键大小顺序排成一个
双向链表
- 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个
双向链表
3.限制
- MySQL数据库目前只有InnoDb数据引擎支持聚簇索引。
- 由于数据物理存储排序方式只能有一种,所以
每个MySQL的表只能有一个聚簇索引
。一般情况下就是该表的主键。 - 如果没有定义主键,InnoDB会选择非空的唯一索引代替,如果没有这样的索引,InnoDB会隐式的定义一个主键来作为聚簇索引。
- 为了充分利用聚簇序索引的聚簇的特性,InnoDB表的
主键列尽量选择有序的顺序id
。
2.2 二级索引(辅助索引、非聚簇索引)
1.实例
聚簇索引只能在搜索条件是主键值
时才能发挥作用,因为B+树中的数据都是按照主键进行排序的,如果我们想以别的列作为搜索条件,可以多建几棵B+树,不同的B+树中的数据采用不同的排列顺序,比如利用第二列
的数据大小作为数据页、页中记录的排序规则,再建一棵B+树,如下:
- 根据这个以第二列数据大小排序的B+树只能确定我们查找记录的主键列,如果我们想根据 第二列的值查到完整数据的话,仍然需要到聚簇索引中再查一遍,这个过程称为
回表
。也就是根据第二列的值查找完整数据需要用到两棵B+树。 - 非聚簇索引的存在不影响数据在聚簇索引中的组织,所以
一张表可以有多个非聚簇索引
。
2.3 比较
- 聚簇索引的叶子节点存储的就是我们的
数据记录
,非聚簇索引的叶子节点存储的是数据位置
。非聚簇索引不会影响数据表的物理存储顺序。 - 一个表只能有一个聚簇索引,因为只能有一种排序存储的方式,但可以有多个非聚簇索引,也就是多个索引目录提供数据检索。
- 使用聚簇索引的时候,数据的查询效率高,但如果对数据进行插入,删除,更新等操作,效率会比非聚簇索引低。
2.4 联合索引
我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按照 c2和c3列
的大小进行排序,这个包含两层含义:
1.先把各个记录和页按照c2列进行排序
2.在记录的c2列相同的情况下,采用c3列进行排序
联合索引本质上也是一个二级索引
2.5 总结
1.为什么不建议使用过长的字段作为主键?
因为所有二级索引都引用主键索引,过长的主键索引会导致二级索引变得过大。
2.InnoDB数据文件本身是一棵B+树,非单调的主键会造成插入新记录时,数据文件为了维持B+树的特性而频繁的分裂调整,十分低效。
2.6 索引的代价
1.空间上的代价
每建立一个索引都要为它建立一棵B+树,每一棵B+树的每一个节点都是一个数据页,一个页默认会占用16KB
的存储空间,一棵很大的B+树由许多数据页组成,那就是很大的一片存储空间。I
2.时间上的代价
每次对表中的数据进行增、删、改操作时,都需要去修改各个B+树索引。B+树每层节点都是按照索引列的值从小到大的顺序排序而组成了双向链表。不论是叶子节点中的记录,还是内节点中的记录(也就是不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序而形成了一个单向链表。而增、删、改操作可能会对节点和记录的排序造成破坏,所以存储引擎需要额外的时间进行一些记录移位、页面分裂、页面回收
等操作来维护好节点和记录的排序。如果我们建了许多索引,每个索引对应的B+树都要进行相关的维护操作,会给性能拖后腿。
第三章 索引的分类和创建
3.1 索引分类
分类方式 | 说明 |
---|---|
功能逻辑 | 普通索引、唯一索引、主键索引、全文索引 |
物理实现方式 | 聚簇索引和非聚簇索引 |
作用字段个数 | 单列索引和联合索引 |
1.普通索引
在创建普通索引时,不附加任何限制条件,只是用于提高查询效率。这类索引可以创建在任何数据类型
中,其值是否唯一和非空,要由字段本身的完整性约束条件决定。建立索引以后,可以通过索引进行查询。例如,在表student
的字段name
上建立一个普通索引,查询记录时就可以根据该索引进行查询。
2.唯一索引
使用UNIQUE
参数可以设置索引为唯一性索引,在创建唯一性索引时,限制该索引的值必须是唯一的
,但允许有空值
。在一张数据表里可以有多个唯一索引
。例如,在表student的字段email中创建唯一性索引,那么字段email的值就必须是唯一的。通过唯一性索引,可以更快速地确定某条记录。
3.主键索引
主键索引就是一种特殊的唯一性索引,在唯一索引的基础上增加了不为空的约束,也就是NOT NUL + UNIQUE
,一张表里最多只有一个
主键索引。Why?这是由主键索引的物理实现方式决定的,因为数据存储在文件中只能按照一种顺序进行存储。
4.单列索引
在表中的单个字段上创建索引。单列索引只根据该字段进行索引。单列索引可以是普通索引,也可以是唯一性索引,还可以是全文索引。只要保证该索引只对应一个字段即可。一个表可以有多个单列索引
。
5.多列索引
多列索引是在表的多个字段组合上创建一个索引。该索引指向创建时对应的多个字段,可以通过这几个字段进行查询,但是只有查询条件中使用了这些字段中的第一个字段时才会被使用。例如,在表中的字段id、name和gender上建立一个多列索引idx_id_name_gender,只有在查询条件中使用了字段id时该索引才会被使用。使用组合索引时遵循最左前缀
集合。
6.全文索引
全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用分词技术等多种算法智能分析出文本文字中关键词的频率和重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。全文索引非常适合大型数据集,对于小的数据集,它的用处比较小。使用参数FULLTEXT
可以设置索引为全文索引。在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值。全文索引只能创建在CHAR
、VARCHAR
或TEXT
类型及其系列类型的字段上,查询数据量较大的字符串类型的字段时,使用全文索引可以提高查询速度。例如,表student的字段information是TEXT类型,该字段包含了很多文字信息。在字段information上建立全文索引后,可以提高查询字段information的速度。随着大数据时代的到来,关系型数据库应对全文索引的需求已力不从心,逐渐被 solr
、ElasticSearch
等专门的搜索引擎所替代。
7.空间索引
使用参数SPATIAL
可以设置索引为空间索引。空间索引只能建立在空间数据类型上,这样可以提高系统获取空间数据的效率。MySQL中的空间数据类型包括GEOMETRY
、POINT
、LINESTRING
和POLYGON
等。目前只有MylSAM存储引擎支持空间检索,而且索引的字段不能为空值。对于初学者来说,这类索引很少会用到。
3.2 创建和删除索引
MySQL支持多种方法在单个或多个列上创建索引:
- 创建表的定义语句
CREATE TABLE
中指定索引列 - 使用
ALTER TABLE
语句在存在的表上创建索引 - 使用
CREATE INDEX
语句在已存在的表上添加索引
1.创建表时创建索引
1.1 创建隐式索引:
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]) [ASC |DESC]
)
UNIQUE
、FULLTEXT
和SPATIAL
为可选参数,分别表示唯一索引、全文索引和空间索引INDEX
与KEY
为同义词,两者的作用相同,用来指定创建索引index_name
指定索引的名称,为可选参数,如果不指定,那么MySQL默认col_name
为索引名col_name
为需要创建索引的字段列,该列必须从数据表中定义的多个列中选择length
为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度ASC
或DESC
指定升序或者降序(在MySQL8.0,InnoDB引擎才有降序)的索引值存储
1.3 创建普通索引
CREATE TABLE book(
book_id INT ,
book_name VARCHAR(100),
authors VARCHAR(100),
info VARCHAR(100) ,
comment VARCHAR(100),
year_publication YEAR,
INDEX(year_publication)
);
// 查看表中索引
SHOW INDEX FROM book;
1.4 创建唯一索引
CREATE TABLE test1(
id INT NOT NULL,
name varchar(30) NOT NULL,
UNIQUE INDEX uk_idx_id(id)
);
声明有唯一索引的字段,在添加数据时,要保证唯一性
2.在已经存在的表上创建索引
2.1 使用ALTER TABLE语句创建索引
ALTER TABLE table_name
ADD [UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name[length],...) [ASC | DESC]
2.2 使用CREATE INDEX创建索引
CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name ON table_name (col_name[length],...) [ASC | DESC]
3.删除索引
3.1 使用ALTER TABLE
删除索引
ALTER TABLE table_name DROP INDEX index_name;
3.2 使用DROP INDEX
语句删除索引
DROP INDEX index_name ON table_name;
第四章 索引的设计原则
为了使索引的使用效率更高,在创建索引时,必须考虑在哪些字段上创建索引和创建什么类型的索引。索引设计不合理或者缺少索引都会对数据库和应用程序的性能造成障碍。高效的索引对于获得良好的性能非常重要。设计索引时,应该考虑相应准则。
4.1 适合创建索引的情形和建议
1.字段的数值有唯一性的限制
索引本身可以起到约束的作用,比如唯一索引、主键索引都是可以起到唯一性约束的,因此在我们的数据表中,如果某个字段是唯一性的,就可以直接创建唯一性索引,或者主键索引。这样可以更快速地通过该索引来确定某条记录。
例如,学生表中学号是具有唯一性的字段,为该字段建立唯一性索引可以很快确定某个学生的信息,如果使用姓名的话,可能存在同名现象,从而降低查询速度。
业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。(来源:Alibaba)
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的
2.频繁作为 WHERE
查询条件的字段
某个字段在SELECT语句的 WHERE 条件中经常被使用到,那么就需要给这个字段创建索引了。尤其是在数据量大的情况下,创建普通索引就可以大幅提升数据查询的效率。
3.经常 GROUP BY
和 ORDER BY
的列
索引就是让数据按照某种顺序进行存储或检索,因此当我们使用 GROUP BY 对数据进行分组查询,或者使用 ORDER BY 对数据进行排序的时候,就需要对分组或者排序的字段进行索引 。如果待排序的列有多个,那么可以在这些列上建立组合索引 。
4.UPDATE
、DELETE
的 WHERE
条件列
对数据按照某个条件进行查询后再进行 UPDATE 或 DELETE 的操作,如果对 WHERE 字段创建了索引,就能大幅提升效率。原理是因为我们需要先根据 WHERE 条件列检索出来这条记录,然后再对它进行更新或删除。如果进行更新的时候,更新的字段是非索引字段,提升的效率会更明显,这是因为非索引字段更新不需要对索引进行维护。
5.DISTINCT
字段需要创建索引
有时候我们需要对某个字段进行去重,使用 DISTINCT,那么对这个字段创建索引,也会提升查询效率。
6.多表 JOIN 连接操作时,创建索引注意事项
连接表的数量尽量不要超过 3 张
,因为每增加一张表就相当于增加了一次嵌套的循环,数量级增长会非常快,严重影响查询的效率。- 对
WHERE 条件创建索引
,因为 WHERE 才是对数据条件的过滤。如果在数据量非常大的情况下,没有 WHERE 条件过滤是非常可怕的。 - 对用于连接的字段创建索引 ,并且该字段在多张表中的
类型必须一致
。比如 course_id (课程编号)在student_info 表和 course 表中都为 int(11) 类型,而不能一个为 int 另一个为 varchar 类型(如果一个为int一个为varchar,也会执行成功,MySQL会做类型转换,但是涉及函数操作,索引失效)。
7.使用列的类型小的创建索引
类型大小
指的就是该类型表示数据范围的大小。
举个例子,如果我们想要对某个整数列建立索引的话,在表示的整数范围允许的情况下,尽量让索引列使用较小的类型,比如我们能使用INT就不要使用BIGINT,能使用MEDIUMINT就不要使用INT。这是因为:
- 数据类型越小,在查询时进行的比较操作越快
- 数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以放下更多的记录,从而减少磁盘I/0带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率。
这个建议对于表的主键
来说更加适用,因为不仅是聚簇索引中会存储主键值,其他所有的二级索引的节点处都会存储一份记录的主键值,如果主键使用更小的数据类型,也就意味着节省更多的存储空间和更高效的I/0。
8.使用字符串前缀创建索引
【 强制 】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。
9.区分度高(散列性高)的列适合作为索引
列的基数
是指某一列中不重复数据的个数。
在记录行数一定的情况下,列的基数越大,该列中的值越分散;列的基数越小,该列中的值越集中。最好为基数大的列建立索引,为基数太小的列建立索引效果可能不太好。
可以使用公式 select count(distinct a) / count(*) from table
计算区分度,越接近1越好。一般超过33%
就算是比较高效的索引了。
一般使用联合索引时将区分度高的列放在前面
。
10.使用最频繁的列放到联合索引的左侧
这样也可以较少的建立一些索引。同时,由于"最左前缀原则",可以增加联合索引的使用率。
11.在多个字段都要创建索引的情况下,联合索引优于单值索引
限制索引的数目
实际工作中,索引的数目不是越多越好。需要限制每张表上的索引数量,建议单张表索引数量不超过6
个。原因:
1.每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
2.索引会影响INSERT
、DELETE
、UPDATE
等语句的性能,因为表中的数据更改的同时,索引也会进行调整和更新,会造成负担。
3.优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,会增加MySQL优化器生成执行计划时间,降低查询性能。
4.2 不适合创建索引的情况
1.在where中使用不到的字段,不要设置索引
WHERE
条件(包括GROUP BY
、ORDER BY
)里用不到的字段不需要创建索引,索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的。
2.数据量小的表最好不要使用索引
如果表记录太少,比如少于1000个,那么是不需要创建索引的。表记录太少,是否创建索引对查询效率的影响并不大。甚至说,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果。
3.有大量重复数据的列上不要建立索引
4.避免对经常更新的表创建过多的索引
5.不建议用无序的值作为索引
6.删除不再使用或者很少使用的索引
7.不要定义冗余或重复的索引
4.3 小结
选择索引的最终目的是为了使
查询
的速度变快,但也会降低插入
和更新
的速度并占用磁盘空间
。