作为一名Java老司机,应该清楚,数据库索引这个知识点在面试中基本上必问,接下来就带你彻底搞懂他
创作不易,你的关注分享就是博主更新的最大动力, 每周持续更新
扫描【企鹅君】公众号二维码免费领取最新独家面试资料,还可以第一时间阅读(比博客早两到三篇)求关注❤️ 求点赞❤️ 求分享❤️ 对博主真的非常重要
该篇已经被GitHub项目收录github.com/JavaDance 欢迎Star和完善
MySQL官方对索引定义:是存储引擎用于快速查找记录的一种数据结构。需要额外开辟空间和数据维护工作。
● 索引是物理数据页存储,在数据文件中(InnoDB,ibd文件),利用数据页(page)存储。
● 索引可以加快检索速度,但是同时也会降低增删改操作速度,索引维护需要代价。
哈希表是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的键即 key,就可以找到其对应的值即 Value。哈希的思路很简单,把值放在数组里,用一个哈希函数把 key 换算成一个确定的位置,然后把 value 放在数组的这个位置。 不可避免地,多个 key 值经过哈希函数的换算,会出现同一个值的情况。处理这种情况的一种方法是,拉出一个链表。 假设,你现在维护着一个身份证信息和姓名的表,需要根据身份证号查找对应的名字,这时对应的哈希索引的示意图如下所示:
图 1
哈希表示意图 图中,User2 和 User4 根据身份证号算出来的值都是 N,但没关系,后面还跟了一个链表。假设,这时候你要查 ID_card_n2 对应的名字是什么,处理步骤就是:首先,将 ID_card_n2 通过哈希函数算出 N;然后,按顺序遍历,找到 User2。 需要注意的是,图中四个 ID_card_n 的值并不是递增的,这样做的好处是增加新的 User 时速度会很快,只需要往后追加。但缺点是,因为不是有序的,所以哈希索引做区间查询的速度是很慢的。 你可以设想下,如果你现在要找身份证号在[ID_card_X, ID_card_Y]这个区间的所有用户,就必须全部扫描一遍了。 所以,哈希表这种结构适用于只有等值查询的场景, 不适合范围查询的场景
二叉搜索树(Binary Search Tree)具有以下特点:
二叉搜索树(Binary Search Tree)是一种基于二叉树的数据结构,它具有以下特点:
当二叉搜索树保持平衡时,即每个节点的左右子树深度相差不超过1,查询操作的时间复杂度为O(log2(N)),具有较高的效率。然而,当二叉搜索树失去平衡时,例如在最坏情况下(有序插入节点),树会退化成线性链表(也被称为斜树),导致查询效率急剧下降,时间复杂度退化为O(N)。
为了维持 O(log(N)) 的查询复杂度,你就需要保持这棵树是平衡二叉树。
AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis。它的查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(logn)。
实际上大多数的数据库存储却并不使用二叉树。其原因是,索引不止存在内存中,还要写到磁盘上。你可以想象一下一棵 100 万节点的平衡二叉树,树高 20。一次查询可能需要访问 20 个数据块。在机械硬盘时代,从磁盘随机读一个数据块需要 10 ms 左右的寻址时间。也就是说,对于一个 100 万行的表,如果使用二叉树来存储,单独访问一个行可能需要 20 个 10 ms 的时间,这个查询可真够慢的。
为了让一个查询尽量少地读磁盘,就必须让查询过程访问尽量少的数据块。那么,我们就不应该使用二叉树,而是要使用“N 叉”树。比如B-树,B+树、红黑树等
这里可不是B减树, 就是B树, 中间只是一个横线
定义:
1、根节点至少包含两个孩子
2、每个节点最多包含m个孩子(m >= 2),m为树的深度
3、除了根节点和叶子节点,其他节点至少有ceil(m/2)个孩子,ceil函数为取上限,例如ceil(1.2)=2,就是小数位多少,都入,不是四舍五入
4、叶子节点的高度相同
如果我们需要寻找key为28的数据,会经历3次磁盘I/O操作过程
PS:
1、我们从上图看到B树和二分搜索树有一点相似的地方,数据是有序的,也就是按照关键字进行排序
2、非叶子节点包含key和value,以及指向其子节点地址的指针
3、叶子节点只有key和value
B+树是B树的一种优化
1、B树的每个结点都存储了key和data,B+树的data存储在叶子节点上。非叶子节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,磁盘IO操作次数更少。查询效率更高
2、B+树查询路径都是从非叶子结点, 到叶子节点。 效率比较稳定
3、B+树叶子结点是一个链表, 扫描全表数据速度更快(只需要遍历叶子节点,并且范围查询也有优化)
因为B+树的这些好处,在 MySQL 中,MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,
但是,两者的实现方式不太一样。MyISAM 引擎是使用的非聚簇索引,InnoDB 引擎使用的是聚簇索引
接下来就介绍下索引类型
CHAR
、VARCHAR
,TEXT
列上可以创建全文索引。效率较低,通常使用搜索引擎如 ElasticSearch 代替。主键索引:是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值(NULL)。一般是在建表的时候同时创建主键索引。
普通索引:仅用于加速查询。
唯一索引:索引列的值必须唯一,但允许有空值(NULL)。如果是组合索引,则列值的组合必须唯一。
联合索引:指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。
覆盖索引:覆盖索引是select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖。
全文索引:全文索引是一种用于文本数据模糊查询的特殊索引,它基于倒排索引实现。目前只有 CHAR
、VARCHAR
,TEXT
列上可以创建全文索引。效率较低,通常使用搜索引擎如 ElasticSearch 代替。
聚簇索引(聚集索引):索引结构和数据一起存放的索引,InnoDB 中的主键索引就属于聚簇索引。
非聚簇索引(非聚集索引):索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
行数据和主键索引存储在一起,辅助键索引只存储辅助键和主键,而不保存数据
InnoDB使用的是聚簇索引,行数据保存在叶子节点上。如果通过where id = 5,直接通过主键索引找到对应的关键字,然后返回行数据
如果通过where name = tom,首先通过辅助键索引找到对应id = 5,然后再通过主键索引找到行数据
PS:
1).如果存在主键,主键就是密集索引
2).如果没有主键,表中第一个唯一非空索引为密集索引
3).如果以上都没有,InnoDB生成一个隐藏主键作为密集索引,是一个6字节的列,随着数据的插入自增
所以,InnoDB必须有个密集索引,这是因为非主键索引叶子节点不保存行数据,而是保存着主键值
B+树叶子节点存储的是指向数据的指针
MyISAM使用的为非聚簇索引,主键索引保存了主键,非主键索引保存了非主键,而数据行保存在其他位置,检索的过程都是通过叶子节点内
保存的地址找到对应的数据行。
从上面索引过程我们可以看到,对于非主键查询来说,聚簇索引需要经过两次检索,好像效率更低了,那么聚簇索引的优势在哪?
1、行数据和叶子节点保存在一起,会一起被加载到内存,找到叶子节点就可以将数据库返回。
2、辅助索引的叶子节点保存主键的指针,而不使用地址值作为指针,减少了当出现行移动或者数据页分裂时辅助索引的维护工作,使用主键
值当作指针会让辅助索引占用更多的空间,InnoDB在移动行时无须更新辅助索引中的这个"指针"。也就是说行的位置(实现中通过16K的Page来定
位,后面会涉及)会随着数据库里数据的修改而发生变化(前面的B+树节点分裂以及Page的分裂),使用聚簇索引就可以保证不管这个主键B+树的
节点如何变化,辅助索引树都不受影响。
InnoDB数据通过page存储,是最小的存储单位,page默认大小为16k,文件系统最小单位块为4k,通过下面参数设置
](https://img2018.cnblogs.com/blog/1351999/201907/1351999-20190709112105625-1669802737.png)
页可以用来存储数据也可以用来存储key和指针,分别对应非叶子节点和叶子节点。通过非叶子节点的二分查找和指针确定数据在哪一页,进
而查到对应的数据。
假设B+树高度为2,一行数据记录大小为1k(实际上很多互联网业务数据就是1k左右),单个叶子节点(页)中记录数16K/1K=16
B+树存在的数据总量 = 根节点节点指针数量 * 每页保存的数据量
假设主键为bigint,长度为8字节,指针大小在InnoDB源码中为6字节,这样一共14字节,我们一个页中存放16384/14=1170。那么一棵高度为
2的B+树,能存放1170*16=18720条这样的数据。
以此类推,一个高度为3的B+树可以存放:1170117016=21902400条这样的记录。
所以在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。通过主键索引查询通常只需要1-3次IO操作即可查找到数据。
MySQL中InnoDB引擎要求每张表都有要有一个聚簇索引(clustered index),也称为主键索引(primary key index),它的作用是将数据按照主键值排序,方便快速地访问单条记录。除了聚簇索引外,MySQL还可以有多辅助(二级)索引(secondary index),它们的作用是加速查询和排序操作。
InnoDB索引有聚簇索引和辅助索引。聚簇索引的叶子节点存储行记录,InnoDB必须要有,且只有一个。
辅助索引的叶子节点存储的是主键值和索引字段值,通过辅助索引无法直接定位行记录,通常情况下,需要扫码两遍索引树。先通过辅助索引定位主键值,然后再通过聚簇索引定位行记录,这就叫做回表查询,它的性能比扫一遍索引树低。
数据表的主键列使用的就是主键索引。B+Tree的叶子节点存放的是主键字段值
通常说的主键索引就是聚簇索引。InnoDB的表要求必须要有聚簇索引:
● 如果表定义了主键,则主键索引就是聚簇索引
● 如果表没有定义主键,则第一个非空unique列作为聚簇索引
● 否则InnoDB会从建一个隐藏的row-id作为聚簇索引
检索过程: 直接通过主键索引找到存储的数据
InnoDB二级索引,也叫作辅助索引,是根据索引列构建 B+Tree结构。但在 B+Tree 的叶子节点中只存了索引列和主键的信息。二级索引占用的空间会比聚簇索引小很多, 通常创建辅助索引就是为了提升查询效率。一个表InnoDB只能创建一个聚簇索引,但可以创建多个辅助索引。
检索过程: 先通过辅助索引找到主键索引, 通过回表查询,然后再找到存储的数据
在MySQL官网,类似的说法出现在explain查询计划优化章节,即explain的输出结果Extra字段为Usingindex时,能够触发索引覆盖。
只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快,这就叫做索引覆盖。实现索引覆盖最常见的方法就是:将被查询的字段,建立到组合索引。
复合索引使用时遵循最左匹配原则,最左匹配顾名思义,就是最左优先,即查询中使用到最左边的列,那么查询就会使用到索引,如果从索引的第二列开始查找,索引将失效。
我们以市民表的联合索引(name, age)为例。如果现在有一个需求:检索出表中“名字第一个字是张,而且年龄是 10 岁的所有男孩”。那么,SQL 语句是这么写的:
mysql> select * from tuser where name like '张%' and age=10 and ismale=1;
你已经知道了前缀索引规则,所以这个语句在搜索索引树的时候,只能用 “张”,找到第一个满足条件的记录 ID3。当然,这还不错,总比全表扫描要好。 然后呢? 当然是判断其他条件是否满足。 在 MySQL 5.6 之前,只能从 ID3 开始一个个回表。到主键索引上找出数据行,再对比字段值。 而 MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
图 3 无索引下推执行流程
图 4 索引下推执行流程
在图 3 和 4 这两个图里面,每一个虚线箭头表示回表一次。 图 3 中,在 (name,age) 索引里面我特意去掉了 age 的值,这个过程 InnoDB 并不会去看 age 的值,只是按顺序把“name 第一个字是’张’”的记录一条条取出来回表。因此,需要回表 4 次。 图 4 跟图 3 的区别是,InnoDB 在 (name,age) 索引内部就判断了 age 是否等于 10,对于不等于 10 的记录,直接判断并跳过。在我们的这个例子中,只需要对 ID4、ID5 这两条记录回表取数据判断,就只需要回表 2 次。
索引字段占用空间越小越好, 大文本,大对象不要创建索引
索引并不是越多越好, 有维护成本,空间成本
建议单表不超过5个索引
频繁更新的字段不适合作为索引
我们创建索引的字段应该是查询操作非常频繁的字段。
频繁需要排序的字段:索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
在 MySQL 中可以通过 EXPLAIN
关键字模拟优化器执行 SQL语句
explain select 列名 FROM 表名 WHERE 条件 ;
EXPLAIN
输出内容如下:
各个字段的含义如下:
列名 | 含义 |
---|---|
id | SELECT的查询序列号 |
select_type | 主要用来说明查询的类型:普通查询、联合查询、子查询等 |
table | 输出的行所引用的表 |
partitions | 如果查询是基于分区表的话,显示查询将访问的分区。,对于未分区的表,值为 NULL |
type | 代表访问类型,是判断sql执行性能比较关键的一个字段 |
possible_keys | MySQL可能使用的键(索引)。 |
key | MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL。 |
key_len | 所选索引的长度 |
ref | 表示索引的哪一列被使用 显示使用哪个列或常数与key一起从表中选择行。 |
rows | 预计要读取的行数 |
filtered | 按表条件过滤后,留存的记录数的百分比 |
Extra | 附加信息 |
type
联接类型。下面给出各种联接类型,按照从最佳类型到最坏类型进行排序:
type代表访问类型,是判断sql执行性能比较关键的一个字段,性能从高到低依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
参考文章:
InnoDB索引原理
《Mysql 实战45讲》
Mysql索引
创作不易,你的关注分享就是博主更新的最大动力, 每周持续更新
微信搜索【 企鹅君 】第一时间阅读(比博客早一到两篇), 关注还能领取资料
求关注❤️ 求点赞❤️ 求分享❤️ 对博主真的非常重要
企鹅君原创|GitHub开源项目github.com/JavaDance 欢迎Star和完善
本文由mdnice多平台发布