前言
本文主要以mysql为研究对象,结合了当前网上主流的文章,个人的理解以及简单整合。基本
存储引擎
一》InnoDB:
事务型数据引擎,支持事务安全表(ACID),支持行锁定和外键。mysql默认的数据引擎二》MyISAM:
它是在 Web、数据仓储和其他应用环境下最常使用的存储引擎之一。 基于 ISAM 存储引擎,并对其进行扩展。 MyISAM 拥有较高的插入、查询速度,但不支持事物。三》MEMORY(Heap)
基于内存存储的引擎,为查询和引用其他表数据提供快速访问。如果数据库重启或崩溃,数据丢失,因此它非 常适合存储临时数据。四》NDB
集群存储引擎,Share Nothing 的架构,能提供更高级 别的高可用性和可扩展性,类似于 Oracle 的 RAC。数据全部放在内存中,因此通过主键查找非常快。复杂的多表关联 操作需要巨大的网络开销,查询速度会很慢五》Archive
非常适合存储归档数据,如日志信息。它仅支持INSERT,SELECT操作,目的是为了提供高速的插入和压缩功能。六》Federated
至少指向一台远程MySQL数据库服务器上的表,不存放数据,类似于Oracle的透明七》Maria
目的取代MyISAM索引
官方定义:索引(Index)是帮助MySQL高效获取数据的数据结构。说白了索引的本质就是一种数据结构
逻辑分类
主键索引普通索引或者单列索引
复合索引(多列索引),遵循最左前缀原理
唯一索引或者非唯一索引
空间索引
前缀索引
物理分类
聚集索引非聚集索引
数据结构分类
b-tree索引哈希索引
全文索引
b+tree索引
常见查询算法与数据结构
大部分优化都是从查询算法方向优化。
1.1 查询算法
1.11 顺序查找
最基本的查询算法,按顺序对比每个元素,量级数据查询效率最低。
对数据结构没有要求,查询复杂度o(n)
1.12 二分查找
比顺序查找更快的算法,其原理从数组中间开始查找,如匹配上则查询结束。
如查询小于中间元素,则从左半边数组中查找,继续通过左数组中间把左数组分割成两部分。
如查询大于中间元素,则从右半边数组中查找,继续通过右数组中间把右数组分割成两部分。
如某一部数组为空,则直接返回找不到。
数据结构要求:有序数组,查询复杂度o(logn)

1.13 二叉排序树查找
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树。
数据结构要求:二叉排序树,查询复杂度o(log2n)
1.14 哈希散列法
根据key值和哈希函数创建一个哈希表(散列表),然后根据键值,通过散列函数,定位数据元素位置。
数据结构要求:哈希表,查询复杂度接近o(1),具体需要根据产生的冲突决定真实的查询复杂度
1.15 B树(Btree)
即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right)
2.所有结点存储一个关键字;
3.非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树

B树的搜索,从根结点开始,如果查询的关键字与结点的关键字相等,那么就命中
否则,如果查询关键字比结点关键字小,就进入左儿子
如果比结点关键字大,就进入右儿子
如果左儿子或右儿子的指针为空,则报告找不到相应的关键字
如果B树的所有非叶子结点的左右子树的结点数目均保持差不多(平衡),那么B树的搜索性能逼近二分查找,
但它比连续内存空间的二分查找的优点是,改变B树结构(插入与删除结点)不需要移动大段的内存数据,
甚至通常是常数开销;
它的搜索时间复杂度为O(log2N),所以它的搜索效率和树的深度有关,如果要提高查询速度,那么就要降低树的深度。要降低树的深度,很自然的方法就是采用多叉树,再结合平衡二叉树的思想,我们可以构建一个平衡多叉树结构,然后就可以在上面构建平衡多路查找算法,提高大数据量下的搜索效率。
1.15 B-tree
B树(Balance Tree)又叫做B- 树(其实B-是由B-tree翻译过来,所以B-树和B树是一个概念) ,它就是一种平衡多路查找树(并不是二叉的)。
B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。
一棵m阶的B-Tree有如下特性:1. 每个节点最多有m个孩子。
2. 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
3. 若根节点不是叶子节点,则至少有2个孩子
4. 所有叶子节点都在同一层,且不包含其它关键字信息
5. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
6. 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
7. ki(i=1,…n)为关键字,且关键字升序排序。
8. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)
B-Tree中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个3阶的B-Tree:

模拟查找关键字29的过程:
1.根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
2.比较关键字29在区间(17,35),找到磁盘块1的指针P2。
3.根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
4.比较关键字29在区间(26,30),找到磁盘块3的指针P2。
5.根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
6.在磁盘块8中的关键字列表中找到关键字29。
分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。
1.16 B+tree
B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。
从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。B+Tree相对于B-Tree有几点不同:
1.非叶子节点只存储键值信息。
2.所有叶子节点之间都有一个链指针。
3.数据记录都存放在叶子节点中。
将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:

数据库中的B+Tree索引可以分为聚集索引(clustered index)和非聚集索引(secondary index)(非聚集索引也可以叫二级索引或者辅助索引)。
上面B+Tree示例图在数据库中的实现即为聚集索引,聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据。
非聚集索引与聚集索引的区别在于非聚集索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过非聚集索引来查询数据时,InnoDB存储引擎会遍历非聚集索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据。
索引数据结构设相关的计算机原理
2.1 两种类型的存储
在计算机系统中一般包含两种类型的存储,计算机主存(RAM)和外部存储器(如硬盘、CD、SSD等)。在设计索引算法和存储结构时,我们必须要考虑到这两种类型的存储特点。主存的读取速度快,相对于主存,外部磁盘的数据读取速率要比主从慢好几个数量级,具体它们之间的差别后面会详细介绍。 上面讲的所有查询算法都是假设数据存储在计算机主存中的,计算机主存一般比较小,实际数据库中数据都是存储到外部存储器的。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。下面详细介绍内存和磁盘存取原理,然后再结合这些原理分析B-/+Tree作为索引的效率。
1.18 主存存取原理
目前计算机使用的主存基本都是随机读写存储器(RAM),现代RAM的结构和存取原理比较复杂,这里本文抛却具体差别,抽象出一个十分简单的存取模型来说明RAM的工作原理。
主存的存取过程如下:
当系统需要读取主存时,则将地址信号放到地址总线上传给主存,主存读到地址信号后,解析信号并定位到指定存储单元,然后将此存储单元数据放到数据总线上,供其它部件读取。写主存的过程类似,系统将要写入单元地址和数据分别放在地址总线和数据总线上,主存读取两个总线的内容,做相应的写操作。
这里可以看出,主存存取的时间仅与存取次数呈线性关系,因为不存在机械操作,两次存取的数据的“距离”不会对时间有任何影响,例如,先取A0再取A1和先取A0再取D3的时间消耗是一样的。
2.2 磁盘存取原理
上文说过,索引一般以文件形式存储在磁盘上,索引检索需要磁盘I/O操作。与主存不同,磁盘I/O存在机械运动耗费,因此磁盘I/O的时间消耗是巨大的磁盘读取数据靠的是机械运动,当需要从磁盘读取数据时,系统会将数据逻辑地址传给磁盘,磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址,即确定要读的数据在哪个磁道,哪个扇区。为了读取这个扇区的数据,需要将磁头放到这个扇区上方,为了实现这一点,磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间,然后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间,最后便是对读取数据的传输。
所以每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分。其中:
寻道时间是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下。
旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms。
传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。
那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右,听起来还挺不错的,但要知道一台500 -MIPS的机器每秒可以执行5亿条指令,因为指令依靠的是电的性质,换句话说执行一次IO的时间可以执行40万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难。
2.3 局部性原理与磁盘预读
由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。
这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。
由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。预读的长度一般为页(page)的整倍数。
页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。
当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。
参考文章
https://blog.csdn.net/zhangyuan19880606/article/details/51217952http://blog.csdn.net/suifeng3051/article/details/49530299?locationNum=1
https://yq.aliyun.com/articles/39841
http://blog.csdn.net/lovelion/article/details/8462814
https://www.cnblogs.com/codehome/p/9356560.html