这是我参与8月更文挑战的第20天,活动详情查看:8月更文挑战
一个sql语句在mysql里究竟是如何运行的呢?又是怎么去查找的呢? 其中就涉及到数据库(存储数据)以及查找算法。 先来看一下几种查找算法;
- 目录查找:类似索引
- 遍历:暴力查找
- 二分:B+树的基础算法
- 键查找:hash查找
索引到底是什么
1、索引是为了加速对表中数据行的检索而创建的一种分散存储的数据结构
2、索引存储在文件里
数据库表的相关文件(存储在本地mysql/data/数据库名/)
table_name_MyISam.frm:表结构文件
table_name_MyISam.MYD:数据文件
table_name_MyISam.MYI:索引文件
学习索引之前,先了解下磁盘存取原理
系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。
InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB,可通过参数innodb_page_size将页的大小设置为4K、8K、16K,在MySQL中可通过如下命令查看页的大小:
mysql> show variables like 'innodb_page_size';
而系统一个磁盘块的存储空间往往没有这么大,因此InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小16KB。InnoDB在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。
为什么要使用索引
要考虑这个问题,首先要从最基础的查找表中数据的过程开始说起。通常我们在查找一个序列中的某一个元素时,用到的最简单的方式就是遍历,数据库也是一样,在一张表中查找某一行数据时,如果不考虑索引的状况下,也会采用一个逐行扫描的方式,只不过数据库通常以块或者页为单位,所以它通常将整个块或者页加载进内存,然后逐块轮询查找到结果并返回。
如果数据库中只有少量数据,那么进行全表扫描,速度还是会很快,但是如果在数据量很大的表中,这种方法就不再适用了,在数据量很大的表中,由于逐行扫描代价变大,通常需要避免采用这种逐行扫描的方式进行数据查找,数据库为了使查询变得高效,所以引入了索引这种方式对数据进行查找。
索引的数据结构
1.二叉树
为了提高I/O效率,先引入最简单的二叉树的索引方式。二叉树,即左子树的键值小于根的键值,右子树的键值大于根的键值,并且左右子树都是二叉排序的。
优点:基于二叉查找树的这种特点,我们在查找某个节点的时候,可以采取类似于二分查找的思想,快速找到某个节点。
对该二叉树的节点进行查找,深度为1的节点的查找次数为1,深度为2的查找次数为2,深度为n的节点的查找次数为n。
缺点:由于二叉查找树可以任意地构造,在极端情况下,如果插入的数据按照递增或者递减的顺序出现,就会变成链表。这棵二叉树的查询效率就低了。因此若想二叉树的查询效率尽可能高,需要这棵二叉树是平衡的。
2.平衡二叉树(AVL树)
平衡二叉树就是为了解决二叉查找树退化成一颗链表而诞生的,平衡树具有如下特点:在符合二叉查找树的条件下,还满足任何节点的两个子树的高度最大差为1。
如果在AVL树中进行插入或删除节点,可能导致AVL树失去平衡,AVL树失去平衡之后,可以通过旋转使其恢复平衡。关于各种旋转,各位可以看书或网上搜索案例,原理不难,但分类多,这里不展开。
优点:解决了二叉查找树退化为近似链表的缺点;
缺点:平衡二叉树追求绝对平衡,条件比较苛刻,导致每次进行插入/删除节点的时候,几乎都会破坏平衡树的第二个规则,每次插入新节点之后需要旋转的次数不能预知。显然,如果在那种插入、删除很频繁的场景中,平衡树需要频繁调整,这使平衡树的性能大打折扣。
3.红黑树
与平衡树不同的是,红黑树在插入、删除等操作,不会像平衡树那样,频繁着破坏红黑树的规则,所以不需要频繁着调整,这也是我们为什么大多数情况下使用红黑树的原因。红黑树原理较为生涩,这里不展开,但是是发展历程之一。
优点:红黑树放弃了追求完全平衡,追求大致平衡,解决了平衡树在插入、删除等操作需要频繁调整的情况。
缺点:数据量大的话,红黑树的深度会很深,也就是说深度不可控,这样一来查找数据还是会很耗时。
4.hash
之前的树,他们的查找,都是先从根节点进行查找,从节点取出数据或索引与查找值进行比较。相较于之前的,哈希索引是一股清流,原理挺不一样的:
哈希表(Hash table,也叫散列表),对于任意给定的键值(Key),通过函数,映射到表中的一个位置,从而实现数据直接访问。
说白了,hash函数就是根据key计算出应该存储地址的位置,而哈希表是基于哈希函数建立的一种查找表。若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。注意,hash表需要解决冲突:对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为冲突。
hash相当于把key通过hash函数计算,得到key的hash值,再用这个hash值做指针,查找hash表中是否存在key,如果存在就返回 key所对应的value,选定一个好的hash函数很重要,好的hash函数可以使计算出的hash值分布均匀,降低冲突,只有冲突减小了,才会降低 hash表的查找时间。
优点:快速查询。相比较于红黑树,hash可以固定“深度”,检索效率非常高,索引的检索可以一次到位。
缺点:
-
哈希索引只包含哈希值和行指针,所以不能用索引中的值来避免读取行
-
哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序和范围查询
-
哈希索引也不支持部分索引列查询,因为哈希索引始终是使用索引列的全部数据进行哈希计算的。
-
哈希索引只支持等值比较查询,如=,IN(),<=>操作
-
如果哈希冲突较多,一些索引的维护操作的代价也会更高 5.B-Tree
B-Tree,平衡多路查找树,如果每个节点,最多有N个孩子,那么这样的树就叫N阶B-Tree,
每个节点中主要包含关键字和指向孩子的指针,最多能有几个孩子,取决于节点的容量和数据库的相关配置,通常情况下这个N是很大的。
B-Tree作为一种数据结构,有如下特征:
- 根节点至少包含两个孩子
- 树中每个节点至多含有N个孩子(N>=2)
- 除根节点和叶节点外,其它每个节点至少有ceil(N/2)个孩子。(ceil表示取上限,例如1.2的上限为2,1.1的上限也为2,非四舍五入)
- 所有叶子节点都位于同一层,即叶子节点的高度都是一样的。
- 假设每个非终端节点包含n个关键字信息(P0,P1…Pn,k1…kn) 6.B+Tree
B+ -Tree是B-Tree的一个变体,其定义基本与B树相同,除了:
- 非叶子节点的子树指针与关键字个数相同,其表明B+树能存储更多的关键字
- 非叶子节点的子树指针P[i],指向关键字值[K[i],K[i+1])的子树。
- 非叶子节点仅用来做索引,数据到保存在叶子节点中。(B+树的所有检索都是从根部开始,直到搜索到叶子节点结束。)
- 所有叶子节点均有一个链指针,指向下一个叶子节点。(方便直接在叶子节点直接做范围统计
B+树相较于B树的优势:
- B+树的磁盘读写代价更低。
- B+树的查询效率更加稳定。
- B+树更有利于对数据库的扫描。