背景
使用mysql最多的就是查询,我们迫切的希望mysql能查询的更快一些,我们经常用到的查询有:
-
按照id查询唯一一条记录
-
按照某些个字段查询对应的记录
-
查找某个范围的所有记录(between and)
-
对查询出来的结果排序
mysql的索引的目的是使上面的各种查询能够更快。
一、预备知识
什么是索引?
上一篇中有详细的介绍,可以过去看一下:什么是索引?
索引的本质:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。
磁盘中数据的存取
以机械硬盘来说,先了解几个概念。
扇区:磁盘存储的最小单位,扇区一般大小为512Byte。
磁盘块:文件系统与磁盘交互的的最小单位(计算机系统读写磁盘的最小单位),一个磁盘块由连续几个(2^n)扇区组成,块一般大小一般为4KB。
磁盘读取数据:磁盘读取数据靠的是机械运动,每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分,寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下;旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms;传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右,听起来还挺不错的,但要知道一台500 -MIPS的机器每秒可以执行5亿条指令,因为指令依靠的是电的性质,换句话说执行一次IO的时间可以执行40万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难。
mysql中的页
mysql中和磁盘交互的最小单位称为页,页是mysql内部定义的一种数据结构,默认为16kb,相当于4个磁盘块,也就是说mysql每次从磁盘中读取一次数据是16KB,要么不读取,要读取就是16KB,此值可以修改的。
数据检索过程
我们对数据存储方式不做任何优化,直接将数据库中表的记录存储在磁盘中,假如某个表只有一个字段,为int类型,int占用4个byte,每个磁盘块可以存储1000条记录,100万的记录需要1000个磁盘块,如果我们需要从这100万记录中检索所需要的记录,需要读取1000个磁盘块的数据(需要1000次io),每次io需要9ms,那么1000次需要9000ms=9s,100条数据随便一个查询就是9秒,这种情况我们是无法接受的,显然是不行的。
二、我们迫切的需求是什么?
我们迫切需要这样的数据结构和算法:
-
需要一种数据存储结构:当从磁盘中检索数据的时候能,够减少磁盘的io次数,最好能够降低到一个稳定的常量值
-
需要一种检索算法:当从磁盘中读取磁盘块的数据之后,这些块中可能包含多条记录,这些记录被加载到内存中,那么需要一种算法能够快速从内存多条记录中快速检索出目标数据
我们来找找,看是否能够找到这样的算法和数据结构。
我们看一下常见的检索算法和数据结构。
三、循环遍历查找
从一组无序的数据中查找目标数据,常见的方法是遍历查询,n条数据,时间复杂度为O(n),最快需要1次,最坏的情况需要n次,查询效率不稳定。
四、二分法查找
二分法查找也称为折半查找,用于在一个有序数组中快速定义某一个需要查找的数据。
原理是:
先将一组无序的数据排序(升序或者降序)之后放在数组中,此处用升序来举例说明:用数组中间位置的数据A和需要查找的数据F对比,如果A=F,则结束查找;如果A<F,则将查找的范围缩小至数组中A数据右边的部分;如果A>F,则将查找范围缩小至数组中A数据左边的部分,继续按照上面的方法直到找到F为止。
示例:
从下列有序数字中查找数字9,过程如下
[1,2,3,4,5,6,7,8,9]
第1次查找:[1,2,3,4,5,6,7,8,9]中间位置值为5,9>5,将查找范围缩小至5右边的部分:[6、7、8、9]
第2次查找:[6、7、8、9]中间值为8,9>8 ,将范围缩小至8右边部分:[9]
第3次查找:在[9]中查找9,找到了。
可以看到查找速度是相当快的,每次查找都会使范围减半,如果我们采用顺序查找,上面数据最快需要1次,最多需要9次,而二分法查找最多只需要3次,耗时时间也比较稳定。
二分法查找时间复杂度是:O(logN)(N为数据量),100万数据查找最多只需要20次(2^20=1048576)
二分法查找数据的优点:定位数据非常快,前提是:目标数组是有序的。
五、有序数组
如果我们将mysql中表的数据以有序数组的方式存储在磁盘中,那么我们定位数据步骤是:
-
取出目标表的所有数据,存放在一个有序数组中
-
如果目标表的数据量非常大,从磁盘中加载到内存中需要的内存也非常大
步骤取出所有数据耗费的io次数太多,步骤2耗费的内存空间太大,还有新增数据的时候,为了保证数组有序,插入数据会涉及到数组内部数据的移动,也是比较耗时的,显然用这种方式存储数据是不可取的。
六、链表
链表相当于在每个节点上增加一些指针,可以和前面或者后面的节点连接起来,就像一列火车一样,每节车厢相当于一个节点,车厢内部可以存储数据,每个车厢和下一节车厢相连。
链表分为单链表和双向链表。