MySQL 高级篇 -- 索引的数据结构

一、简介

  • 定义
  1. MySQL 官方对索引的定义为:索引(Index)是帮助 MySQL 高效获取数据的数据结构
  2. 索引的本质:索引是数据结构。你可以简单理解为排好序的快速查找数据结构,满足特定查找算法。这些数据结构以某种方式指向数据, 这样就可以在这些数据结构的基础上实现高级查找算法
  • 优点
  1. 类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本,这也是创建索引最主要的原因。
  2. 通过创建唯一索引,可以保证数据库表中每一行数据的唯一性
  3. 在实现数据的参考完整性方面,可以加速表和表之间的连接。换句话说,对于有依赖关系的子表和父表联合查询时,可以提高查询速度。
  4. 在使用分组和排序子句进行数据查询时,可以显著减少查询中分组和排序的时间,降低了CPU的消耗。
  • 缺点
  1. 创建索引和维护索引要耗费时间,并且随着数据量的增加,所耗费的时间也会增加。
  2. 索引需要占磁盘空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间, 存储在磁盘上 ,如果有大量的索引,索引文件就可能比数据文件更快达到最大文件尺寸。
  3. 虽然索引大大提高了查询速度,同时却会降低更新表的速度。当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。
  • 空间上的代价
  1. 每建立一个索引都要为它建立一棵B+树,每一棵B+树的每一个节点都是一个数据页。
  2. 一个页默认会占用 16KB 的存储空间,一棵很大的B+树由许多数据页组成,那就是很大的一片存储空间。
  • 时间上的代价
  1. 每次对表中的数据进行增、删、改操作时,都需要去修改各个B+树索引。
  2. B+树每层节点都是按照索引列的值 从小到大的顺序排序 而组成了双向链表。不论是叶子节点中的记录,还是内节点中的记录(也就是不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序而形成了一个单向链表。
  3. 增、删、改操作可能会对节点和记录的排序造成破坏,所以存储引擎需要额外的时间进行一些记录移位、页面分裂、页面回收等操作来维护好节点和记录的排序。

二、InnoDB 索引结构

2.1 设计索引

  • 新建一个表
mysql> CREATE TABLE index_demo(
    ->   c1 INT,
    ->   c2 INT,
    ->   c3 CHAR(1),
    ->   PRIMARY KEY(c1)
    -> ) ROW_FORMAT = Compact;
  • 格式示意图

在这里插入图片描述

字段说明
record_type记录头信息的一项属性,表示记录的类型, 0 表示普通记录、 2 表示最小记录、 3 表示最大记录、 1 暂时保留。
next_record记录头信息的一项属性,表示下一条地址相对于本条记录的地址偏移量,我们用箭头来表明下一条记录是谁。
各个列的值这里只记录在 index_demo 表中的三个列,分别是 c1 、 c2 和 c3 。
其他信息除了上述3种信息以外的所有信息,包括其他隐藏列的值以及记录的额外信息。

在这里插入图片描述

  • 把一些记录放到

在这里插入图片描述

  • 给所有的页建立一个 目录项

下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。

在这里插入图片描述

  1. 页28为例,它对应目录项2,这个目录项中包含着该页的页号 28以及该页中用户记录的最小主键值 5
  2. 只需要把几个目录项在物理存储器上连续存储(比如:数组),就可以实现根据主键值快速查找某条记录的功能。
  3. 比如查找主键值为20的记录:先从目录项中根据二分法快速确定出主键值为20的记录在目录项3中(因为12 < 20 < 209),它对应的页是页9;再根据前边说的在页中查找记录的方式去页9中定位具体的记录。
  4. 至此,针对数据页做的简易目录就搞定了。这个目录有一个别名,称为索引

2.2 索引的层级迭代

  • 迭代1次:目录项纪录的页

新分配了一个编号为30的页来专门存储目录项记录。
在这里插入图片描述

  • 迭代2次:多个目录项纪录的页

在这里插入图片描述

  • 迭代3次:目录项记录页的目录页

在这里插入图片描述

  • 小结
  1. 例中存放用户记录的页最多存放3条记录,存放目录项记录的页最多存放4条记录。
  2. 真实环境中一个页存放的记录数量是非常大的,假设所有存放用户记录的叶子节点代表的数据页可以存放100条用户记录,所有存放目录项记录的内节点代表的数据页可以存放1000条目录项记录。
    2.1 如果B+树只有1层,也就是只有1个用于存放用户记录的节点,最多能存放 100 条记录。
    2.2 如果B+树有2层,最多能存放 1000×100=10,0000 条记录。
    2.3 如果B+树有3层,最多能存放 1000×1000×100=1,0000,0000 条记录。
    2.4 如果B+树有4层,最多能存放 1000×1000×1000×100=1000,0000,0000 条记录。

2.3 常见索引

索引按照物理实现方式,索引可以分为 2 种:聚簇(聚集)和非聚簇(非聚集)索引。我们也把非聚集索引称为二级索引或者辅助索引。

2.3.1 聚簇索引

  • 特点
  1. 使用 记录主键值 的大小进行记录和页的排序,这包括三个方面的含义:
    1.1 页内的记录是按照主键的大小顺序排成一个单向链表
    1.2 各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表
    1.3 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表
  2. B+树的叶子节点存储的是完整的用户记录。所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。
  • 优点
  1. 数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快
  2. 聚簇索引对于主键的排序查找和范围查找速度非常快
  3. 按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以节省了大量的io操作
  • 缺点
  1. 插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个 自增的ID列为主键
  2. 更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义 主键为不可更新
  3. 二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。

2.3.2 二级索引(辅助索引、非聚簇索引)

  • c2 列的大小顺序构建

在这里插入图片描述

  • 回表
  1. 根据这个以 c2 列大小排序的B+树只能确定我们要查找记录的主键值;
  2. 如果我们想根据 c2 列的值查找到完整的用户记录的话,仍然需要到聚簇索引中再查一遍,这个过程称为 回表
  3. 也就是根据 c2 列的值查询一条完整的用户记录需要使用到 2 棵 B+树!

在这里插入图片描述

2.3.3 联合索引

  1. 可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引
  2. 比如让B+树按照c2和c3列的大小进行排序,即先把各个记录和页按照c2列进行排序,在记录的c2列相同的情况下,采用c3列进行排序;
  3. 以c2和c3列的大小为排序规则建立的B+树称为联合索引,本质上也是一个二级索引,只会生成一颗B+树,仍需回表

三、MyISAM 索引结构

  • B树索引适用存储引擎
索引 / 存储引擎MyISAMInnoDBMemory
B-Tree索引支持支持支持
  1. 即使多个存储引擎支持同一种类型的索引,但是他们的实现原理也是不同的。
  2. Innodb 和 MyISAM 默认的索引是 Btree 索引;而 Memory 默认的索引是 Hash 索引。
  3. MyISAM 引擎使用B+Tree作为索引结构,叶子节点的 data 域存放的是 数据记录的地址

3.1 实现原理

  • 原理图

在这里插入图片描述

  • 二级索引的结构图

在这里插入图片描述

3.2 与 InnoDB 对比

MyISAM的索引方式都是“非聚簇”的,与InnoDB包含1个聚簇索引是不同的。

在这里插入图片描述

  1. 在InnoDB存储引擎中,我们只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录,而在 MyISAM 中却需要进行一次回表操作,意味着MyISAM中建立的索引相当于全部都是二级索引
  2. InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。
  3. InnoDB的非聚簇索引data域存储相应记录主键的值,而MyISAM索引记录的是地址。换句话说,InnoDB的所有非聚簇索引都引用主键作为data域。
  4. MyISAM的回表操作是十分快速的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。
  5. InnoDB要求表必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。

四、数据结构选择的合理性

4.1 Hash 结构

在这里插入图片描述

  1. 哈希函数h有可能将两个不同的关键字映射到相同的位置,这叫做碰撞,在数据库中一般采用链接法来解决;
  2. 在链接法中,将散列到同一槽位的元素放在一个链表中,当链表长度 > 8 时转为红黑树,如下图所示:
    在这里插入图片描述
  • Hash 索引的适用性
    在这里插入图片描述
  1. 采用自适应 Hash 索引目的是方便根据 SQL 的查询条件加速定位到叶子节点,特别是当 B+ 树比较深的时候,通过自适应 Hash 索引可以明显提高数据的检索效率。
  2. 可以通过innodb_adaptive_hash_index变量来查看是否开启了自适应 Hash:
# Hash indexes are built on demand for the pages of the index that are accessed often.
# If InnoDB notices that queries could benefit from building a hash index, it does so automatically.

mysql>  show variables like '%adaptive_hash_index';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_adaptive_hash_index | ON    |
+----------------------------+-------+
1 row in set (0.08 sec)

mysql> 

4.2 二叉搜索树

如果我们利用二叉树作为索引结构,那么磁盘的IO次数和索引树的高度是相关的。

在这里插入图片描述

为了提高查询效率,就需要减少磁盘IO数。为了减少磁盘IO的次数,就需要尽量降低树的高度,需要把原来“瘦高”的树结构变的“矮胖”,树的每层的分叉越多越好。避免出现以下情况:

在这里插入图片描述

4.3 AVL 树

  • 平衡二叉树
    在这里插入图片描述

  • 平衡三叉树

在这里插入图片描述

4.3 B-Tree

在这里插入图片描述

  • 特性
  1. 根节点的儿子数的范围是 [2,M]。
  2. 每个中间节点包含 k-1 个关键字和 k 个孩子,孩子的数量 = 关键字的数量 +1,k 的取值范围为 [ceil(M/2), M]。
  3. 叶子节点包括 k-1 个关键字(叶子节点没有孩子),k 的取值范围为 [ceil(M/2), M]。
  4. 假设中间节点节点的关键字为:Key[1], Key[2], …, Key[k-1],且关键字按照升序排序,即 Key[i] < Key[i+1]。此时 k-1 个关键字相当于划分了 k 个范围,也就是对应着 k 个指针,即为:P[1], P[2], …, P[k],其中 P[1] 指向关键字 小于 Key[1] 的子树,P[i] 指向关键字属于 (Key[i-1], Key[i]) 的子树,P[k] 指向关键字大于 Key[k-1] 的子树。
  5. 所有叶子节点位于同一层。
  • 查找:关键字 9
  1. 我们与根节点的关键字 (26,35)进行比较,9 小于 17 那么得到指针 P1;
  2. 按照指针 P1 找到磁盘块 2,关键字为(8,12),因为 9 在 8 和 12 之间,所以我们得到指针 P2;
  3. 按照指针 P2 找到磁盘块 6,关键字为(9,10),然后我们找到了关键字 9。

4.4 B+ Tree

  • B+ 树和 B 树的差异
  1. 有 k 个孩子的节点就有 k 个关键字。也就是孩子数量 = 关键字数,而 B 树中,孩子数量 = 关键字数 +1。
  2. 非叶子节点的关键字也会同时存在在子节点中,并且是在子节点中所有关键字的最大(或最小)。
  3. 非叶子节点仅用于索引,不保存数据记录,跟记录有关的信息都放在叶子节点中。而 B 树中,非叶子节点既保存索引,也保存数据记录
  4. 所有关键字都在叶子节点出现,叶子节点构成一个有序链表,而且叶子节点本身按照关键字的大小从小到大顺序链接。

4.5 R 树

R-Tree在MySQL很少使用,仅支持 geometry 数据类型,支持该类型的存储引擎只有myisam、bdb、innodb、ndb、archive几种。

索引 / 存储引擎MyISAMInnoDBMemory
R-Tree 索引支持支持不支持
  • 举例:查找20英里以内所有的餐厅
  1. 一般情况下我们会把餐厅的坐标(x,y)分为两个字段存放在数据库中,一个字段记录经度,另一个字段记录纬度;遍历所有的餐厅获取其位置信息,然后计算是否满足要求;如果一个地区有100家餐厅的话,我们就要进行100次位置计算操作了;如果应用到谷歌、百度地图这种超大数据库中,这种方法便必定不可行了。
  2. R树就很好的解决了这种高维空间搜索问题。它把B树的思想很好的扩展到了多维空间,采用了B树分割空间的思想,并在添加、删除操作时采用合并、分解结点的方法,保证树的平衡性。
  3. 因此,R树就是一棵用来存储高维数据的平衡树,相对于B-Tree,R-Tree的优势在于范围查找。

4.6 算法的时间复杂度

同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法

在这里插入图片描述

  • 9
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值