MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
Mysql在存储数据之外,数据库系统各种还维护着满足特定查找算法的数据结构,这些数据结构以某种引用(指向)表中的数据,这样我们就可以通过数据结构上实现的高级算法来快速找到我们想要的数据,而这种数据结构就是索引
简单理解为"排好序的可以快速查找数据的数据结构"。
索引就好比字典上的目录
索引概述
如果在查询的数据表没有索引,那么SQL语句查询会从第一条记录开始匹配判断,直至匹配成功,而数据库查询数据是从磁盘查询的,每一次匹配都是一次IO流操作,这样的查询被称为全表扫描,及其耗费性能!
有索引的情况下,以二叉树数据结构演示为例,如果现在表中有ID,NAME,AGE,三条属性,现在对AGE建立索引,那么就需要维护这样一个二叉树,当我们往这张表插入数据时,就需要维护二叉树的节点,这个节点是指向这一行数据的地址 如下图所示

然后我们就可以根据二叉树查找的算法来匹配数据,这种方法十分的高效。
注意事项:上述二叉树索引结构只是一个示意图,并不是真实的索引结构
索引优势
-
提高数据检索的效率,降低数据库的IO成本
-
通过索引对数据进行排序,降低数据排序的成本,降低了CPU的消耗
索引劣势
索引实际上也是一张表,保存了主键和索引的字段,并且指向实体表的记录,所以索引 也是需要占用磁盘空间的。
在索引大大提高查询速度的同时,却会降低表的更新速度,在对表进行数据增删改的同时,MySQL不仅要更新数据,还需要保存一下索引文件,每次更新添加了的索引列的字段,都会去调整因为更新带来的减值变化后的索引的信息。
索引数据结构(一般都是指B-Tree)
介绍
MySQL的索引是在引擎层实现的,主要包含以下几种:
索引结构 | 描述 |
---|---|
B+Tree索引 | 最常见的索引类型,大部分引擎都支持B+树索引 |
Hash索引 | 底层数据使用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询 |
R-Tree(空间索引) | 是MyISAM存储引擎的一种特殊索引类型,主要用于管理地理空间类型,使用较少 |
Full-text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式,类似于Lincene,Solr |
索引结构 | InnoDB | MySIAM | Memory |
---|---|---|---|
B+Tree索引 | 支持 | 支持 | 支持 |
Hash索引 | 不支持 | 不支持 | 支持 |
R-Tree(空间索引) | 不支持 | 支持 | 不支持 |
Full-text(全文索引) | 5.6版本支持 | 支持 | 不支持 |
平时大多数的索引,如果没有特别指明,都是指B+树结构组织的索引
索引结构
MySQL的数据是存储在磁盘中的,每次查询数据语句都需要将数据从磁盘中加载到内存中,,相当于进行了一次IO操作,而磁盘IO操作十分耗时,所以我们优化SQL的方向就是降低IO操作的次数,访问二叉树的每个节点就会发生一次IO,如果想要减少磁盘IO操作,就需要尽量降低树的高度。那如何降低树的高度呢?
- 二叉树

弊端:当极端情况下,数据递增插入是,会一直向右插入,形成链表,查询效率会降低
解决方法:红黑树是弱平衡树,通过红黑树解决树的平衡问题
但红黑树也是二叉树,也存在大数据量情况下,层级较深,检索速度慢
- B-Tree(多路平衡查找树)
以一课最大度数(度数:一个节点的子节点个数)为5(5阶)的B-Tree为例(每个节点最多存储4个key,5个指针):

插入数据的变化过程:
该树为5阶B-Tree,一个节点有5个指针,4个key
首先插入234,345,23,899,现在这个节点已经有4个key,继续添加1200,节点已经不能装下了,树会发生裂变,中间元素向上分裂
则会变为

,继续添加1234,比345大,则会向右边插入,比1200大,则会放在1200的右边,再插入1500,还是放在右边,此时右边的节点已经已经有4个key了,继续添加1000,右边节点有5个key,继续分裂,中间元素向上分裂,1200为中间数,向上与345为同一个节点

,继续插入234,346,都放在左边,此时左边的节点已经已经有4个key了,继续插入12,中间元素向上分裂,123变为中间元素,向上分裂

,继续插入1567,1800,都放在右边,此时右边的节点已经已经有4个key了,继续插入1980,1567变为中间元素向上分裂

,现在上面的节点已经有4个key了,继续插入2000,1888,放在右边,此时右边的节点已经已经有4个key了,在插入2456,1980变成中间元素向上分裂,上面的节点也要在向上分裂,1200为中间元素向上分裂,最后得

,现在树的层级来到了三层,如果要存储数据,每一个数据是挂在key下的,
- B+Tree
以一颗最大度数为4阶的B+tree为例:

插入数据变化过程:
该树为5阶B+Tree,一个节点有5个指针,4个key
首先插入232,234,567,1000,现在这个节点已经有4个key,在插入1234,中间元素向上分裂,即567向上分裂,B+树的所有元素都会出现在叶子节点上,

,继续插入1234,此时右边已经有4个key了,继续插入2345,中间元素1000向上分裂,

,插入100,200,在插入35,中间元素200向上分裂,

,以此类推,而下面链表的首元素就是向上分离的中间元素
MySQL索引中的B+Tree结构
参考博客:一文搞懂MySQL索引所有知识点(建议收藏)_一文搞懂mysql索引所有知识点 敖丙-CSDN博客
B树的缺点:
-
B树不支持范围查询的快速查找,你想想这么一个情况如果我们想要查找10和35之间的数据,查找到15之后,需要回到根节点重新遍历查找,需要从根节点进行多次遍历,查询效率有待提高。
-
如果data存储的是行记录,行的大小随着列数的增多,所占空间会变大。这时,一个页中可存储的数据量就会变少,树相应就会变高,磁盘IO次数就会变大。
Mysql索引数据结构对经典的B+Tree进行了优化,在原来的基础上,增加一个指向相邻叶子结点的链表指针,就形成了带有顺序指针的B+Tree,提高访问区间的性能。利于数据库中的排序

B+树的最底层叶子节点包含了所有的索引项。
从图上可以看到,B+树在查找数据的时候,由于数据都存放在最底层的叶子节点上,所以每次查找都需要检索到叶子节点才能查询到数据。
所以在需要查询数据的情况下每次的磁盘的IO跟树高有直接的关系,
但是从另一方面来说,由于数据都被放到了叶子节点,所以放索引的磁盘块锁存放的索引数量是会跟着增加的。
所以相对于B树来说,B+树的树高理论上情况下是比B树要矮的。也存在索引覆盖查询的情况,在索引中数据满足了当前查询语句所需要的全部数据,此时只需要找到索引即可立刻返回,不需要检索到最底层的叶子节点。
等值查询
假如我们查询值等于9的数据。查询路径磁盘块1->磁盘块2->磁盘块6。
-
第一次磁盘IO:将磁盘块1加载到内存中,在内存中从头遍历比较,9<15,走左路,到磁盘寻址磁盘块2。
-
第二次磁盘IO:将磁盘块2加载到内存中,在内存中从头遍历比较,7<9<12,到磁盘中寻址定位到磁盘块6。
-
第三次磁盘IO:将磁盘块6加载到内存中,在内存中从头遍历比较,在第三个索引中找到9,取出data,如果data存储的行记录,取出data,查询结束。如果存储的是磁盘地址,还需要根据磁盘地址到磁盘中取出数据,查询终止。(这里需要区分的是在InnoDB中Data存储的为行数据,而MyIsam中存储的是磁盘地址。)
范围查询
假如我们想要查找9和26之间的数据。查找路径是磁盘块1->磁盘块2->磁盘块6->磁盘块7。
-
首先查找值等于9的数据,将值等于9的数据缓存到结果集。这一步和前面等值查询流程一样,发生了三次磁盘IO。
-
查找到15之后,底层的叶子节点是一个有序列表,我们从磁盘块6,键值9开始向后遍历筛选所有符合筛选条件的数据。
-
第四次磁盘IO:根据磁盘6后继指针到磁盘中寻址定位到磁盘块7,将磁盘7加载到内存中,在内存中从头遍历比较,9<25<26,9<26<=26,将data缓存到结果集。
-
主键具备唯一性(后面不会有<=26的数据),不需再向后查找,查询终止。将结果集返回给用户。
可以看到B+树可以保证等值和范围查询的快速查找,MySQL的索引就采用了B+树的数据结构。
B+Tree和B-Tree的区别:
-
B树:非叶子节点和叶子节点都会存储数据。
-
B+树:只有叶子节点才会存储数据,非叶子节点只存储键值,起到索引的效果。叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。
Hash(哈希索引)
哈希索引是采用一定的Hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储到hash表上
如果两个(或多个)键值映射到一个相同的槽位上,他们就产生了hash冲突,可以通过链表来解决
-
特点
-
Hash索引只能用于对等比较(= ,in),不支持范围查询(between,>,<,....)
-
hash表是无序,无索引的,无法利用索引完成排序操作
-
查询效率高,通常指需要一次检查就可以了,效率通常高于B+Tree索引
-
-
存储引擎支持
在MySQL中,支持Hash索引的是Memory引擎,而InnoDB中具有自适应Hash功能,hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的
面试题:为什么InnoDB存储引擎选择B+Tree索引结构?
-
相对于二叉树,层级更少,而层级意味着IO操作的多少,我们优化SQL的方向就是降低IO操作的次数,即降低树的高度(层级数)
-
B+树只有叶子节点存放数据,其余节点不存放,而一个节点就是MySQL中的一页(Page),而其余节点(Page)存放的指针和key也就增多,那么在相同数据量的情况下,B+树的层级更少,那么性能就越高,
-
且B树的范围匹配的效率很低,需要每次都返回根节点查询,而B+树在查询首个数据之后可以直接在叶子节点中遍历需要的数据,
-
相对于Hash索引,B+树支持范围匹配和排序操作
索引使用场景
推荐建立索引:
-
主键自动建立唯一索引
-
频繁作为查询条件的字段应该创建索引(where后面的语句)
-
查询中与其他表关联的字段,外键关系建立索引
-
多字段查询下 倾向创建联合索引
-
查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
-
查询中 统计或者分组字段
不推荐建立索引:
-
表记录太少
-
经常增删改查
-
where条件里用不到的字段不建立索引
索引分类
分类 | 含义 | 特点 | 关键字 |
---|---|---|---|
主键索引 | 针对表中主键创建的索引 | 默认自动创建,只能有一个 | primary key |
唯一索引 | 避免同一个表中某数据列中重复 | 可以有多个 | unique |
常规索引 | 快速定位特定数据 | 可以有多个 | |
全文索引 | 全文索引查找的是文本中的关键词,而不是比较索引中的值 | 可以有多个 | fulltext |
在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种
分类 | 含义 | 特点 |
---|---|---|
聚集索引(Clustered Index)(B+树) | 将数据存储与索引放在一块,索引结构的叶子节点保存了行数据 | 必须有。而且只能有一个 |
二级索引(辅助索引)(Secondary index) | 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
聚集索引的选举规则:
-
如果存在主键,主键索引就是聚集索引
-
如果没有主键,将使用第一个唯一索引作为聚集索引
-
如果表没有主键,或者没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。
聚集索引叶子节点存放的是行数据
二级索引中叶子节点存放的是指向聚集索引叶子节点的指针
聚集索引与二级索引的叶子节点存放的数据的关系就是指针与二级指针的关系
如下图:

如果查询的是name字段的话,那么数据库进行的就是回表查询
数据库会先走二级索引,通过二级索引找到对应的主键值,再根据主键值到聚集索引当中拿到行数据
思考:InnoDB主键索引的B+Tree高度为多高?
假设:一行数据大小为1k,一页中可以存储16行这样的数据,InnoDB的指针占用6个字节的空间,主键即使为bigint,占用字节数为8.
如果树的高度为2:
由于B+树只有叶子节点才存储数据
16 * 1024 = n * 8 +(n+1 )*6
得出:n = 1170,每一个节点的key为1171个
得出存储的记录为:11701* 16 * =18736
高度为3时
1171 * 1171 * 16 =21939386条记录
索引语法
- 创建索引
sql
create [unique|fulltext] index index_name on table_name (index_col_name,...);
如果一个索引只关联一个字段,被称为单列索引 。如果一个索引关联了多个字段,则被称为联合索引(组合索引)。
- 查看索引
sql
show index from table_name
- 删除索引
sql
drop index index_name on table_name
案例:
前置代码:
sql
create database if not exists Demo;
use Demo;
create table if not exists `user`(
`id` int(4) not null auto_increment comment 'ID',
`name` varchar(20) not null comment '姓名',
`phone` varchar(11) not null comment '电话号码',
`email` varchar(20) default null comment '邮箱',
`profession` varchar(20) default null comment '专业',
`age` int(4) not null comment '年龄',
`gender` tinyint(1) default null comment '性别,取值为0或1',
`status` int(4) not null comment '状态 取值为0与6之间',
`createTime` datetime not null comment '创建时间',
primary key (`id`)
)engine = innodb default charset = utf8;
INSERT INTO `user` (`name`, `phone`, `email`, `profession`, `age`, `gender`, `status`, `createTime`) VALUES
('王伟', '13800138001', '[email protected]', '计算机科学', 25, 1, 1, '2023-01-01 10:00:00'),
('李娜', '13800138002', '[email protected]', '软件工程', 28, 0, 3, '2023-01-02 11:00:00'),
('张敏', '13800138003', '[email protected]', '数据科学', 22, 0, 2, '2023-01-03 12:00:00'),
('刘强', '13800138004', '[email protected]', '人工智能', 30, 1, 6, '2023-01-04 13:00:00'),
('陈静', '13800138005', '[email protected]', '网络安全', 26, 0, 4, '2023-01-05 14:00:00'),
('杨光', '13800138006', '[email protected]', '计算机科学', 24, 1, 5, '2023-01-06 15:00:00'),
('赵琳', '13800138007', '[email protected]', '软件工程', 27, 0, 1, '2023-01-07 16:00:00'),
('黄磊', '13800138008', '[email protected]', '数据科学', 29, 1, 0, '2023-01-08 17:00:00'),
('吴芳', '13800138009', '[email protected]', '人工智能', 23, 0, 2, '2023-01-09 18:00:00'),
('周涛', '13800138010', '[email protected]', '网络安全', 31, 1, 3, '2023-01-10 19:00:00'),
('徐洋', '13800138011', '[email protected]', '计算机科学', 25, 1, 4, '2023-01-11 20:00:00'),
('孙丽', '13800138012', '[email protected]', '软件工程', 28, 0, 5, '2023-01-12 21:00:00'),
('马超', '13800138013', '[email protected]', '数据科学', 22, 1, 6, '2023-01-13 22:00:00'),
('朱婷', '13800138014', '[email protected]', '人工智能', 30, 0, 1, '2023-01-14 23:00:00'),
('胡军', '13800138015', '[email protected]', '网络安全', 26, 1, 2, '2023-01-15 10:00:00'),
('林小燕', '13800138016', '[email protected]', '计算机科学', 24, 0, 3, '2023-01-16 11:00:00'),
('郭峰', '13800138017', '[email protected]', '软件工程', 27, 1, 4, '2023-01-17 12:00:00'),
('何洁', '13800138018', '[email protected]', '数据科学', 29, 0, 5, '2023-01-18 13:00:00'),
('高翔', '13800138019', '[email protected]', '人工智能', 23, 1, 6, '2023-01-19 14:00:00'),
('罗娟', '13800138020', '[email protected]', '网络安全', 31, 0, 0, '2023-01-20 15:00:00'),
('郑凯', '13800138021', '[email protected]', '计算机科学', 25, 1, 1, '2023-01-21 16:00:00'),
('谢芳', '13800138022', '[email protected]', '软件工程', 28, 0, 2, '2023-01-22 17:00:00'),
('董明', '13800138023', '[email protected]', '数据科学', 22, 1, 3, '2023-01-23 18:00:00'),
('萧红', '13800138024', '[email protected]', '人工智能', 30, 0, 4, '2023-01-24 19:00:00'),
('曹阳', '13800138025', '[email protected]', '网络安全', 26, 1, 5, '2023-01-25 20:00:00'),
('邓超', '13800138026', '[email protected]', '计算机科学', 24, 1, 6, '2023-01-26 21:00:00'),
('许晴', '13800138027', '[email protected]', '软件工程', 27, 0, 0, '2023-01-27 22:00:00'),
('彭宇', '13800138028', '[email protected]', '数据科学', 29, 1, 1, '2023-01-28 23:00:00'),
('蒋雯', '13800138029', '[email protected]', '人工智能', 23, 0, 2, '2023-01-29 10:00:00'),
('蔡明', '13800138030', '[email protected]', '网络安全', 31, 1, 3, '2023-01-30 11:00:00);
案例要求:
-
name字段为姓名字段,该字段的值可能会重复,为该字段创建索引
-
phone手机号字段的值是非空的,且唯一的,为该字段创建唯一索引。
-
为profession、age、status创建联合索引
-
为email创建合适的索引来提升查询效率
代码展示:
sql
show index from user;
create index `idx_user_name` on `user`(`name`);
create unique index `idx_user_phone` on `user`(`phone`);
create index `id_user_pas` on `user`(`profession`,`age`,`status`);
create index `idx_user_email` on `user`(`email`);
drop index `idx_user_email` on `user`;

注意事项:
在使用联合索引时,放置字段的顺序是有讲究的,使用最频繁的字段放在左侧,根据使用频繁程度从重到轻,要遵循"最左前缀原则"
以上是对索引本身的了解以及如何建立索引,下一篇则是索引的性能分析,使用原则以及设计原则,希望对大家有所帮助!让我们一同进步!