【MySQL笔记】索引 (非常重要)
1. 什么是索引?
MySQL的索引是一种数据结构,它可以帮助数据库高效地查询、更新数据表中的数据。索引通过一定的规则排列数据表中的记录,使得对表的查询可以通过对索引的搜索来加快速度。
2. 为什么使用索引?
就是为了提高查询数据的效率。
3. 索引应该使用哪种数据结构?
分析:
- Hash表
时间复杂度为O(1),虽然非常快,但是MySQL没有使用Hash表,因为Hash表不支持范围查询。- 二叉搜索树
虽然支持范围查询,但是可能会退化为一个单边树,时间复杂度为O(n);
即使是在正常情况下:
由于数据是在磁盘上保存的,每一次访问树的子节点都会发生一次磁盘I/O;
磁盘I/O是制约数据库性能的主要因素;
二叉树的度是2,在数据量变大的时候,无法保证树高,树越高I/O的次数就越多,性能就越低;
因此MySQL并没有使用二叉搜索树;- N叉树(B树)
每个节点都可以超过两个子节点,可以解决树高的问题,从而提升I/O的性能
时间复杂度为O(logN)
但MySQL也没有使用N叉树,而是采用了更为高效的B+树
B+树
以4阶B+树为例:

时间复杂度:O(logN)
可以有效的控制树高
叶子结点之间是一个双向循环链表
B+树和B树的区别?
- B+树的叶子节点之间有一个相互连接的引用,可以通过一个叶子节点找到它相邻的兄弟节点;
MySQL在组织叶子节点的时候使用的是双向链表- B+树的非叶子节点的值都包含在叶子节点中
MySQL非叶子节点只保存了对子节点的引用,没有保存真实的数据,所有的真实数据全都在叶子节点中保存- 对于B+树而言,在相同树高的情况下,查找任一元素的时间复杂度都一样,性能均衡
索引在整个数据检索的过程中是如何工作的?
这就要从MySQL存储结构说起,也就是MySQL组织数据的方式。
4. MySQL中的页 (了解)
在使用 InnoDB 存储引擎的 MySQL 数据库中,表中的数据都会保存在.ibd文件中,在.ibd 文件中最重要的结构体就是Page(页),页是内存与磁盘交互的最小单元,默认大小为16KB,每次内存与磁盘的交互至少读取一页,所以在磁盘中每个页内部的地址都是连续的。
4.1 使用页的原因
因为在使用数据的过程中,根据局部性原理,将来要使用的数据大概率与当前访问的数据在空间上是临近的,所以一次从磁盘中读取一页的数据放入内存中,当下次查询的数据还在这个页中时就可以从内存中直接读取,从而减少磁盘I/O提高性能。
4.2 为什么要定义每页的大小为16kb?
Linux操作系统中管理文件的最小单位是4KB,MySQL作为一个数据库程序,用4KB大小管理显然太小了,所以定义了16KB的大小;
一个16KB的页落盘是以4KB为单位写入的,最后拼成一个16KB的页,如果在过程中发生异常,部分数据就会丢失,但是在数据真正落盘之前会记录各种日志,可以根据日志来恢复文件。

页的结构:文件头,文件尾,页主体

4.3 页文件头和页文件尾

其中,上一页页号和下一页页号这两个属性可以把页与页之间链接起来,形成一个双向链表。
B+树中,每一个节点就是一个页。

4.4 页主体
页主体部分是保存真实数据的主要区域,每当创建一个新页,都会自动分配两个行,一个是页内最小行 Infimun,另一个是页内最大行 Supremun,这两个行并不存储任何真实信息,而是做为数据行链表的头和尾,第一个数据行有一个记录下一行的地址偏移量的区域next_record将页内所有数据行组成了一个单向链表,此时新页的结构如下所示:
当向一个新页插入数据时,将Infimun连接第一个数据行,最后一行真实数据行连接 Supremun,这样数据行就构建成了一个单向链表,更多的行数据插入后,会按照主键从小到大的顺序进行链接,如下图所示;

4.5 页目录

4.6 数据页头

5. B+ 树在MySQL索引中的应用
非叶子节点保存索引数据,叶子节点保存真实数据,如下图所示:

以查找id为5的记录,完整的检索过程如下:
- 首先判断B+树的根节点中的索引记录,此时 5<7,应访问左孩子节点,找到索引页2;
- 在索引页2中判断id的大小,找到与5相等的记录,命中,加载对应的数据页。
以上的I/O过程,加载索引页1 -->加载索引页2 --> 加载数据页3,只有3次磁盘I/O,查询效率极高,也可以将索引页缓存到内存中,这样1次I/O就能找到目标数据,进一步提升查询性能;
5.1 计算三层树高的B+树可以存放多少条记录

- 假设一条数据记录大小为1KB,那么一个16KB的数据页,就可以保存16条数据;

- 索引页中存的是主键值和子节点的引用,也就是下一个节点的偏移量 (地址);

主键一般是 bigint 是 8byte,源码中写的下一页地址为 6 byte,也就是说一条索引记录占了 8+6=14byte
3. —个索引页可以存 16* 1024 / 14 = 1170 个,也就说一个索引的度是1170,可以有1170个子节点;
4. 理论上一个三层树高的 B+ 树可以存 1170*1170*16 = 21,902,400 条记录
在当前的场景下,表中有2000W+条数据,通过三次 I/O 就可以完成数据的查找,而且性能均衡。
6. 索引分类
6.1 主键索引
- 如果为表定义了主键,则自动创建主键索引,也叫做聚集索引;
- 如果没有为表定义主键索引,innodb会使用唯一列构建索引树
- 如果没有唯一列,则自动生成一个ROW_ID构建索引树
6.2 普通索引
- 最基本的索引类型,没有唯一性的限制。
- 可以为多列创建组合索引,称为复合索引或组合索引
组合索引:索引中包含多个列,列在索引中的排序按创建时指定的顺序排序,同时每个索引行都会包含主键值

为了提升查询效率,可以为查询频繁的列创建普通索引 where name =‘xxx’
可以为name列单独创建一个索引,如果手动创建了索引,都会生成一个与之对应的索引树(B+树),会占用磁盘空间。
6.3 唯一索引
- 当在一个表上定义一个唯一键 UNIQUE 时,自动创建唯一索引,也会创建一棵索引引树;
- 与普通索引类似,但区别在于唯一索引的列不允许有重复值。
6.4 全文索引
基于文本列(CHAR、VARCHAR或TEXT列)上创建,以加快对这些列中包含的数据查询和 DML 操作,用于全文搜索,仅MyISAM和InnoDB引擎支持。不建议使用
有专门的文档数据库,可以高效的处理文档搜索,比如mongodb
6.5 聚集索引
- 与主键索引是同义词;
- 如果没有为表定义
PRIMARY KEY, InnoDB使用第一个UNIQUE和NOT NULL的列作为聚集索引; - 如果表中没有
PRIMARY KEY或合适的UNIQUE索引,InnoDB会为新插入的行生成一个行号并用6字节的ROW_ID字段记录,ROW_ID单调递增,并使用ROW_ID做为索引。
6.6 非聚集索引
- 聚集索引以外的索引称为非聚集索引或二级索引;
- 二级索引中的每条记录都包含该行的主键列,以及二级索引指定的列;
- InnoDB使用这个主键值来搜索聚集索引中的行,这个过程称为回表查询。
6.6.1 回表查询
以普通索引的列为条件查到主键值,再用主键值去主键索引树中查学生的详细信息,涉及到两个索引树,这个过程叫回表查询(回到主键索引树)。

6.7 索引覆盖
当一个 select 语句使用了普通索引且查询列表中的列刚好是创建普通索引时的所有或部分列,这时就可以直接返回数据,而不用回表查询,这样的现象称为索引覆盖;

7. 手动创建索引
7.1 主键索引

7.2 唯一索引

7.3 普通索引
创建的时机:
1.创建表的时候,明确知道某些列是频繁查询的列,就直接创建(当表中的数据比较少的时候,全表扫描的效率可能比使用索引的效率还要高),在工作中,一般当表中的数据量到了十几万的时候才创建索引;
2.随着业务的不断发展,在版本迭代的过程中添加索引;

推荐的创建方式:

7.4 创建复合索引
创建语法与创建普通索引相同,只不过指定多个列,列与列之间用逗号隔开


7.5 删除索引
7.5.1 主键索引
alter table 表名 drop primary key;
删除主键索引时不用指定索引名,因为表中只有一个主键;
删除之前要把自增取消,如果存在自增列则报错;
-- 删除主键索引,等于删除表中的主键
alter table t_index6 modify id bigint; // 后边不写自增表示取消自增
alter table t_index6 drop PRIMARY KEY;
7.5.2 其他索引
alter table 表名 drop index 索引名;
// 删除其他索引
alter table t_index6 drop index name;
alter table t_index6 drop index idx_index6_sno_classId;
7.6 创建索引的注意事项
- 索引应该创建在高频查询的列上;
- 索引需要占用额外的存储空间;(因为每个索引都会生成一个索引树)
- 对表进行插入、更新和删除操作时,同时也会修改索引,可能会影响性能;(因为要维护索引|树)
- 创建过多或不合理的索引会导致性能下降,需要谨慎选择和规划索引。



