MySQL(三)MySQL索引原理
数据库索引,是数据库管理系统(DBMS)中一个排序的数据结构,以协助快速查询、更新数据库表中数据。
-----维基百科对数据库索引的定义
拿汉语字典的目录页(索引)打比方,我们可以按拼音、笔画、偏旁部首等排序的目录(索引)快速查找到需要的字。
索引类型
在InnoDB 里面,索引类型有三种,普通索引、唯一索引(主键索引是特殊的唯一索引)、全文索引。
普通(Normal)索引:也叫非唯一索引,是最普通的索引,没有任何的限制。
唯一(Unique)索引:唯一索引要求键值不能重复。另外需要注意的是,主键索引是一种特殊的唯一索引,它还多了一个限制条件,要求键值不能为空。主键索引用primay key创建。
全文(Fulltext)索引:针对比较大的数据,比如我们存放的是消息内容,有几KB的数据的这种情况,如果要解决like查询效率低的问题,可以创建全文索引。只有文本类型的字段才可以创建全文索引,比如char、varchar、text。
MyISAM和InnoDB都支持全文索引。
索引结构
二分查找
对于提高查询效率的办法,最常见的就是二分查找法,这在生活中也有非常多的应用。
每一次,我们都把候选数据缩小了一半。对于已经排序过的数据,采用这种方式效率比较高。
如果考虑以有序数组作为索引的数据结构,对于等值查询和比较查询效率确实会比较高,但是在更新数据(删除,新增)的时候可能需要挪动大量的数据(改变其index),所以只适合用来保存静态的数据。
为了对于频繁的插入/删除也有比较高的效率,就需要采用链表来实现。如果是简单的单链表,那么它的查找效率是比较差的。
想要兼具数组和链表的优势,就需要一个新的数据结构----二叉查找树
二叉查找树(BST Binary Search Tree)
二叉查找树就是满足 : 左子树所有的节点都小于父节点,右子树所有的节点都大于父节点的数据结构,将其投影到平面上以后,就是一个有序的线性表。
二叉查找树既能够实现快速查找,又能够实现快速插入。
但是二叉查找树有一个问题:它的查找耗时是和这棵树的深度相关的,在最坏的情况下,根节点为最大或者最小的时候会变成斜树,也就是单链表,时间复杂度会退化成O(n)
因为左右子树深度差太大,导致其查询效率跟普通单链表一样
所以我们就需要一个更加平衡的二叉树 --- 平衡二叉树 Balanced binary search trees
平衡二叉树(AVL Tree)
平衡二叉树要求左右子树深度差绝对值不能超过1。
如果此时我们依次插入1,2,3,4,5,6,其树型结构会是如下图所示,不会出现斜树的情形https://www.cs.usfca.edu/~galles/visualization/AVLtree.html
使用上述链接演示一下可以发现,当AVL树出现斜树的情形时,就会发生旋转
在平衡二叉树中,一个节点是一个大小固定的单位,作为索引应该存储什么内容?
第一个是索引的键值。比如我们在id上创建了一个索引,在用where id=1的条件查询的时候就会找到索引里面的id的这个键值。
第二个是数据的磁盘地址。因为索引的作用就是方便我们快速查找数据的存放地址。
第三个是左右子节点的引用
当我们使用AVL树存储索引数据时,访问一个节点就要跟磁盘之间发生一次IO。
InnoDB操作磁盘的最小的单位是一页(或者叫一个磁盘块),大小是16K(16384字节)。那么一个树的节点大小最大就是16K的大小。
如果我们一个节点只存一个键值+数据+引用,例如整型的字段,可能只用了十几个或者几十个字节,它远远达不到16K的容量,所以访问一个树节点,进行一次IO的时候,浪费了大量的空间(明明可以读取16K的数据)。
其次,当数据量非常大的时候,这个AVL树的高度也会非常高,导致需要和磁盘进行非常多次的IO,很消耗时间
所以就需要在AVL树的基础上再次进行优化
1.让每个节点存储更多的数据。
2.节点上的关键字的数量越多,我们的指针数也越多,也就是意味着可以有更多的分叉(“路数”)。
多路平衡查找树(B Tree)
跟AVL树一样,B树在枝节点和叶子节点存储键值、数据地址、节点引用。
它有一个特点:分叉数(路数)永远比关键字数多1。
https://www.cs.usfca.edu/~galles/visualization/BTree.html
如图就是一个多路平衡查找树(Degree 节点拥有的子树= 4)
比如MaxDegree(路数)= 4的时候,我们插入数据1、2、3、4,在插入4的时候,本来应该在第一个磁盘块,但是如果一个节点有四个关键字的时候,意味着有5个指针,子节点会变成5 路,所以这个时候必须进行分裂。把中间的数据2提上去,把1变成左子树节点,3、4变成2的右子树节点。
如果删除节点,会有相反的合并的操作
从B树的特性不难发现,在更新索引的时候也会发生大量的索引结构的调整,这就是为什么建议我们不要在频繁更新的列上建索引,或者为什么不要更新主键。
加强版多路平衡查找树(B+树)
实际上,MySQL使用的索引还不是B树,而是使用了B树的改良版本--B+树
其存储结构如下所示:
MySQL中的B+Tree有几个特点:
1、它的关键字的数量是跟路数相等的;
2、B+Tree的根节点和枝节点中都不会存储数据,只有叶子节点才存储数据。搜索到关键字不会直接返回,会到最后一层的叶子节点。比如我们搜索 id=28,虽然在第一层直接命中了,但是全部的数据在叶子节点上面,所以还是要继续往下搜索,一直到叶
子节点。
举个例子:假设一条记录是1K,一个叶子节点(一页)可以存储16条记录。我们计算下非叶子节点可以存储多少个指针?
假设索引字段是bigint 类型,长度为 8 字节。指针大小在 InnoDB 中设置为6 字节,这样一共 14 字节。非叶子节点(一页)可以存储16384/14=1170个这样的单元(键值+指针),代表有1170个指针。
树 深 度 为 2 的 时 候 , 有 1170^2 个 叶 子 节 点 , 可 以 存 储 的 数 据 为1170*1170*16=21902400。
在查找数据时一次IO读取一页的数据,也就是说,一张2000万左右的表,查询数据最多需要访问3次磁盘
3.B+Tree的每个叶子节点增加了一个指向相邻叶子节点的指针
它的最后一个数据会指向下一个叶子节点的第一个数据,形成了一个有序链表的结构。如果是范围查询,在查到一端的最小值之后,可以直接遍历这个链表的结构,直接获取到所有的值不需要重新从根节点进行IO查找
4、它是根据左闭右开的区间 [ )来检索数据。
总结一下InnoDB中B+树的优点
1)它是B树的变种,也解决B树解决的问题:每个节点存储更多关键字和更多路数
2) 扫库、扫表能力更强
如果我们要对表进行全表扫描,只需要遍历叶子节点就可以了,不需要遍历整棵B+树拿到所有的数据
3)B+树的磁盘读写能力相对于B树来说更强
根节点和枝节点不保存数据,所以一个节点可以保存更多的关键字,一次磁盘加载的关键字更多
4)排序能力更强
因为叶子节点上有下一个数据区的指针,数据形成了链表
5)效率更加稳定
B+树永远是在叶子节点拿到数据,所以IO次数是稳定的
不同存储引擎的索引结构
每张InnoDB 的表会有两个文件(.frm和.ibd),MyISAM的表会有三个文件(.frm、.MYD、.MYI)。
.frm是MySQL里面表结构定义的文件,不管你建表的时候选用任何一个存储引擎都会生成
InnoDB
在InnoDB 里面,它是以主键为索引来组织数据的存储的,所以索引文件和数据文件是同一个文件,都在.ibd文件里面
在InnoDB的主键索引的叶子节点上,它直接存储了表中的数据。
InnoDB中的主键索引又称为聚集索引,非主键都是非聚集索引。
所谓聚集索引就是索引键值的逻辑顺序跟表数据行的物理存储顺序是一致的。(比如字典的目录是按拼音排序的,内容也是按拼音排序的,按拼音排序的这种目录就叫聚集索引)。
对于非主键索引(辅助索引)存储的是辅助索引和主键值。如果使用辅助索引查询,会先根据辅助索引查找到对应的主键值,再根据主键值在主键索引中查询,最终取得数据。
因为B+树在实现一个节点存储多个关键字,并保持平衡的过程中会发生分叉和合并的操作,这个时候键值的地址会发生变化,所以在辅助索引里面不能存储键值的磁盘地址。
因为InnoDB是以主键为索引来组织数据的存储,如果一张表没有主键怎么办?
1.如果我们定义了主键(PRIMARY KEY),那么InnoDB会选择主键作为聚集索引。
2.如果没有显式定义主键,则InnoDB会选择第一个不包含NULL值的唯一索引作为主键索引。
3.如果也没有这样的唯一索引,则InnoDB会选择内置6字节长的ROWID作为隐藏的聚集索引,它会随着行记录的写入而递增
MyISAM
MyISAM有两个额外的文件: .MYD和.MYI
.MYD文件,D代表Data,是MyISAM的数据文件,存放数据记录; .MYI文件,I代表Index,是MyISAM的索引文件,存放索引
在MyISAM里面,索引和数据是两个独立的文件。
在MyISAM的B+Tree 里面,叶子节点存储的是数据文件对应的磁盘地址。所以从索引文件.MYI中找到键值后,会到数据文件.MYD中获取相应的数据记录。
在MyISAM中,主键索引和辅助索引的检索方式都是一样的,都是先在索引文件里面找到磁盘地址,然后到数据文件里面根据磁盘地址获取对应数据