主流索引查找算法
- 线性查找,是一个链表,要搜索的话,需要从第一个往后一个个找
- 二分查找,是线性查找的升级,也就是说二分查找是可以用线性查找的数据接口,但是算法不一样
- 二叉树查找,进一步提高性能,引入了二叉树
- 平衡二叉树,二叉树因为有平衡的问题,又进一步出现了平衡二叉树
- B树,在平衡二叉树之后又发现了一个问题,之前的数据结构每一个节点都是一个行数据,这样的话对于磁盘的利用率是有问题的,因为我的数据要最终落到磁盘上,以一个节点为单位去读取磁盘效率是很低的
- B+树,B树效率也有问题,出现了B+树,可以理解B+树是B树和线性的结合
算法原理动画模拟地址:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
线性查找
首先结构是一个链表,比如从1-100找id是5的,要一个个的找,时间复杂度跟数据表量的大小有关,表越大,时间越长,时间复杂度O(N)
原理:从第一个开始,逐个匹配
二分查找
结构同样也是一个链表,只不过每次查找的时候,把链表一分为二,找到链表中间的位置后,把查找目标和中间的位置做比较,假如目标比中间位置小,那么从链表开始位置到链表结束为止作为再次查找的区间再次一分为二的查找,直到找到为止,复杂度是O(logN)
原理:拿出有序数列中点位置作为比较对象,根据中点数据大小,选取一半数据作为新的数据,每次可以将数量减小一半
问题:必须要知道这一段数据的中点在哪里,但在很多实际情况是不知道的,比如磁盘随机读写的情况下,比如一个表有1000W行数据,在磁盘里并不是连续读写,因为磁盘很多碎片,找不到中间位置,然后就升级了二叉树
二叉树查找
比如要找0017这个数,从根节点找(0006),因为0017比0006大,那么根节点开始从右侧找,找到0009,0017比0009还大,那么继续从右边找,直到找到为止。复杂度O(logN)
问题:有可能退化为线性查找,就比如上图,数字越来越大,00017一直往右,那么就不平衡了
平衡二叉树
查找的原理和二叉树一样,只不过它会在增删改查的时候自动旋转,使数据结构树保持平衡(最好看动画模拟了解更透彻)
问题:一个节点所包含的数据量太低,一个节点只有一条数据,一行的数据都在一个节点里,在计算机系统里,不管是内存还是硬盘,读写度都有一个最小的单位,比如机械磁盘最小单位在512B,如果是SSD那么最小读写单位是4K-8K之间,也就是每次存取数据至少要4K的数据,如果存储的不足4K,那么有两种选择,一种是和别的数据一起存储在一个4K的块里,另一种是浪费4K的块存储这么一点数据。数据库里一般不大,一条数据占了4K磁盘的块,对于现在的数据库是无法接受的,所以B树就这么衍生
B树
在平衡二叉树的基础上,每个节点多放几条数据,比如把id是6、7、8、9的几行数据都堆在一个节点上,一个几点的数据也就大了,就可以沾满一个块了,提高了磁盘的利用率,另外一次存取这么大的时候,减少了硬盘的读写,因为一次把十几行数据和读十几次把十几行数据读取,效率是不一样的。
每个节点有三个指针,分别指向不同区间的节点,每个节点的数据结构是指针+数据+指针+数据+指针这样一个交替的数据结构,查找的方式首先是在根节点线性查找。比如上图找0012,首先在根节点0003-0011,0012比0011大,那么找0011右侧的节点,然后0012比0013小,那么找第二层0013-0015左边的,至于为什么,是因为它的结构约束的 。每个几点记住都是线性查找,也就是遍历。
B树是线性数据结构和树的结合;B树通过多数据节点大大降低了树的高度;B树不需要旋转就可以保持树平衡
但是B树对范围查找的效率是非常低的,由此进化出B+树
B+树:
上两层存的都是索引指针,最下层叶子节点才存储的是数据,上面都是指针指向下面叶子节点的数据,叶子节点之间用箭头行程了链表,所以范围查询非常高效
根据上图我们来看下 B+ 树和 B 树有什么不同:
①B+ 树非叶子节点上是不存储数据的,仅存储键值,而 B 树节点中不仅存储键值,也会存储数据。
之所以这么做是因为在数据库中页的大小是固定的,InnoDB 中页的默认大小是 16KB。
如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的 IO 次数又会再次减少,数据查询的效率也会更快。
另外,B+ 树的阶数是等于键值的数量的,如果我们的 B+ 树一个节点可以存储 1000 个键值,那么 3 层 B+ 树可以存储 1000×1000×1000=10 亿个数据。
一般根节点是常驻内存的,所以一般我们查找 10 亿数据,只需要 2 次磁盘 IO。
②因为 B+ 树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。
那么 B+ 树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而 B 树因为数据分散在各个节点,要实现这一点是很不容易的。
有心的读者可能还发现上图 B+ 树中各个页之间是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。
其实上面的 B 树我们也可以对各个节点加上链表。这些不是它们之前的区别,是因为在 MySQL 的 InnoDB 存储引擎中,索引就是这样存储的。
也就是说上图中的 B+ 树索引就是 InnoDB 中 B+ 树索引真正的实现方式,准确的说应该是聚集索引。
通过上图可以看到,在 InnoDB 中,我们通过数据页之间通过双向链表连接以及叶子节点中数据之间通过单向链表连接的方式可以找到表中所有的数据。
MyISAM 中的 B+ 树索引实现与 InnoDB 中的略有不同。在 MyISAM 中,B+ 树索引的叶子节点并不存储数据,而是存储数据的文件地址。
MySQL 中的 Hash 索引
采用 Hash 进行检索效率非常高,基本上一次检索就可以找到数据,而 B+ 树需要自顶向下依次查找,多次访问节点才能找到数据,中间需要多次 I/O 操作,从效率来说 Hash 比 B+ 树更快。
我们来看下 Hash 索引的示意图:
键值 key 通过 Hash 映射找到桶 bucket。在这里桶(bucket)指的是一个能存储一条或多条记录的存储单位。一个桶的结构包含了一个内存指针数组,桶中的每行数据都会指向下一行,形成链表结构,当遇到 Hash 冲突时,会在桶中进行键值的查找。
那么什么是 Hash 冲突呢?
如果桶的空间小于输入的空间,不同的输入可能会映射到同一个桶中,这时就会产生 Hash 冲突,如果 Hash 冲突的量很大,就会影响读取的性能。
通常 Hash 值的字节数比较少,简单的 4 个字节就够了。在 Hash 值相同的情况下,就会进一步比较桶(Bucket)中的键值,从而找到最终的数据行。
Hash 值的字节数多的话可以是 16 位、32 位等,比如采用 MD5 函数就可以得到一个 16 位或者 32 位的数值,32 位的 MD5 已经足够安全,重复率非常低。
我们模拟一下 Hash 索引。关键字如下所示,每个字母的内部编码为字母的序号,比如 A 为 01,Y 为 25。我们统计内部编码平方的第 8-11 位(从前向后)作为 Hash 值:
Hash 索引与 B+ 树索引的区别
我们之前讲到过 B+ 树索引的结构,Hash 索引结构和 B+ 树的不同,因此在索引使用上也会有差别。
Hash 索引不能进行范围查询,而 B+ 树可以。这是因为 Hash 索引指向的数据是无序的,而 B+ 树的叶子节点是个有序的链表。
Hash 索引不支持联合索引的最左侧原则(即联合索引的部分索引无法使用),而 B+ 树可以。对于联合索引来说,Hash 索引在计算 Hash 值的时候是将索引键合并后再一起计算 Hash 值,所以不会针对每个索引单独计算 Hash 值。因此如果用到联合索引的一个或者几个索引时,联合索引无法被利用。
Hash 索引不支持 ORDER BY 排序,因为 Hash 索引指向的数据是无序的,因此无法起到排序优化的作用,而 B+ 树索引数据是有序的,可以起到对该字段 ORDER BY 排序优化的作用。同理,我们也无法用 Hash 索引进行模糊查询,而 B+ 树使用 LIKE 进行模糊查询的时候,LIKE 后面前模糊查询(比如 % 开头)的话就可以起到优化作用。
对于等值查询来说,通常 Hash 索引的效率更高,不过也存在一种情况,就是索引列的重复值如果很多,效率就会降低。这是因为遇到 Hash 冲突时,需要遍历桶中的行指针来进行比较,找到查询的关键字,非常耗时。所以,Hash 索引通常不会用到重复值多的列上,比如列为性别、年龄的情况等。
巩固
mysql支持的索引类型
Btree索引
使用的是b+树的结构存储数据,在b+树中每个叶子节点都包括指向下一个叶子节点的指针,这样方便进行叶子节点之间的遍历。
Btree索引特点?
1:Btree索引实际上是B+树的结构存储数据。
对于不同的存储引擎,实现有不同,比如myisam索引在叶子节点是通过数据的物理位置来引用行的,而innodb是通过主键来引用被索引的行
2:Btree索引可以加快数据的查询速度。
通常索引的大小远小于表中数据大小,使用Btree索引,存储引擎不需要全表扫描获取数据,取而代之的是从索引的根节点开始搜索,在索引的根节点存放了指向下层子节点的指针,存储引擎根据指针向下层查找,通过比较节点页的值和要查找的值就可以拿到合适的指针进入下层的子节点,而这些指针实际上是定义了子节点中值得上限和下限,所以呢存储引擎最终要找到对应的值就可以判断值是否存在,最终存储引擎通过Btree索引找到符合要求的叶子节点,叶子节点比较特别,它的指针是指向被索引的数据,而不是其他叶子节点。innodb指向的是主键,而myisam指向的是数据的物理地址。
3:Btree索引更适合范围查找
由于Btree索引对索引是顺序存储的,所以Btree索引适合范围查找
Btree索引在什么情况下才能被使用到?
1:全局匹配的查询
比如在order_sn创建索引,要查找order_sn=‘9877772000’的订单,那么这就是全局匹配查询
2:匹配最左前缀查询
没有单独在order_sn创建索引,而是创建一个order_sn和order_date的联合索引,对于上面的条件还是可以用联合索引,只要是联合索引的第一列符合条件,那么会被使用到;但查order_date=某某日,那么这就不会使用索引
3:匹配列前缀查询
指的是可以匹配某一列的开头部分,比如查找order_sn like \'987%\',这也可以使用order_sn的联合索引。
4:匹配范围值得查询
order_sn > \'987777\' and order_sn < \'99999\'
5:精确匹配左前缀,并范围匹配另外一列
order_sn=\'987777\' and order_date >\'2000-10-11\'
6:只访问索引的查询(覆盖索引)
也就是查询只需要查索引,无需访问行。
7:排序。
Btree索引的限制有哪些?
1:命中的数据占用了表中大部分数据,那么mysql查询优化器会认为全表扫描方式更好。
2:不是按照最左原则,无法使用索引。
3:使用索引时不能跳过索引中的列(这里是说的是左边的列,而不是最左边的列)
比如联合索引(a,b,c),查询的时候查a=1 and c=2,那么这里只能使用a列
4:not in 和 <>也无法使用索引
5:如果查询中有某个列的范围查询,则其右边所有的列都无法使用索引。
Hash索引
支持hash索引的类型有innodb和memory引擎。
Hash索引特点?
1:Hash索引是基于Hash表实现,只有查询条件精确匹配Hash索引中的所有列时,才能够使用到Hash索引。
也就是说,Hash索引只能用在等值查询,那么范围和模糊查询就不可以了
2:对于Hash索引中的所有列,存储引擎都会为每一行计算一个Hash码,Hash索引中储存的就是Hash码。
Hash索引表中保存每一个Hash索引所代表的数据行的指针,由于Hash索引本身只存储Hash码,所以Hash索引结构比较紧凑,那么查询速度比较快。
Hash面临的问题有哪些?
1:使用Hash索引查找数据必须经过两次读取,因为Hash索引包括的是hash码、对应行指针、键值,所以先找到行,再通过行读取。
2:无法排序
3:全键值匹配,不支持部分匹配
4:有可能产生hash冲突,为了保证查找效率,一般hash码比较小,这样但容易冲突;但身份证可以比较适合建立hash索引
使用索引的好处有哪些?
1:索引大大减少了存储引擎需要扫描的数据量。
索引文件的大小远小于数据文件的大小。比如innodb引擎发生一次io最小的单位是以叶为单位,一页可以存储的信息越多,那么读取效率月越快,默认innodb页大小为16K,由于索引的大小通常远比一行的大小小的多,所以一页内可以存储更多的索引数据,因此通过索引查找读取页的数量少,这样也就减少了存储引擎扫描的数量,加快了速度。
2:索引可以帮我们进行排序,避免使用临时表,因为索引是有序的。
3:索引可以把随机I/O变为顺序I/O,因为索引是有序的。