MySQL是网上非常流行的数据库,其底层存储引擎和数据检索引擎的设计非常重要,尤其是MySQL数据的存储形式和索引的设计索引决定了MysqlRetrieval的整体数据性能。我们知道索引的作用是快速检索数据,而快速检索的本质是数据结构。通过选择不同的数据结构,可以快速检索出各种数据。在数据库中,高效的搜索算法非常重要,因为数据库中存储着大量的数据,高效的索引可以节省大量的时间。比如下面的数据表,如果Mysql没有实现索引算法,那么要查找id=7的数据,只能通过暴力顺序遍历找到id=7的数据,需要比较7次,如果这个表存了1000W的数据,要找到id=1000W的数据,需要比较1000W次,这是不能接受的。MySQL索引底层数据结构选择1.哈希表(Hash)哈希表是快速数据检索的有效工具。哈希算法:也叫散列算法,是将任意值(key)通过哈希函数转换成一个固定长度的key地址,并用这个地址来执行特定的数据数据结构。考虑这个数据库表user,表中有7条数据,我们需要检索id=7的数据,SQL语法为:select\*fromuserwhereid=7;hash算法首先计算出id=7的数据存放的物理地址addr=hash(7)=4231,4231映射到的物理地址为0x77,0x77是id=7存放的数据的物理地址,通过这个独立地址可以找到user_name='g'对应的数据。这就是哈希算法快速检索数据的计算过程。但是hash算法有一个数据冲突的问题,就是hash函数对于不同的key可能会计算出相同的结果,比如hash(7)可能会和hash(199)计算出相同的结果,即不同的key映射到同样的结果,这就是碰撞问题。解决冲突问题的常用方法是链地址法,即用一个链表将发生冲突的数据一个接一个地连接起来。计算出hash值后,还需要检查hash值是否有碰撞数据链表,如果有则遍历链表尾部,直到找到真正key对应的数据。从算法的时间复杂度分析,哈希算法的时间复杂度为O(1),检索速度非常快。比如查找id=7的数据,只需要计算一次哈希索引就可以得到对应的数据,检索速度非常快。但是Mysql并没有使用哈希作为底层算法,为什么呢?因为考虑到常用的数据检索手段是范围搜索,比如下面的SQL语句:select\*fromuserwhereid\>3;对于上面的语句,我们要做的是找出id>3的数据,这是非常典型的范围查找。如果使用哈希算法实现的索引,如何进行范围查找呢?一个简单的思路是一次性把所有的数据都找出来加载到内存中,然后在内存中过滤目标范围内的数据。但是这种范围搜索的方法过于繁琐,效率也很低。因此,哈希算法实现的索引虽然可以快速检索数据,但是不能对数据进行高效的范围搜索,所以哈希索引不适合作为Mysql底层索引的数据结构。2.二叉搜索树(BST)二叉搜索树是一种支持快速数据搜索的数据结构,如下图所示:二叉搜索树的时间复杂度为O(lgn),例如对于上面的二叉树树形结构,我们需要计算比较3次才能检索到id=7的数据,比直接遍历查询节省了一半的时间,在检索效率上可以实现高速检索。另外,二叉树的结构能否解决哈希索引无法提供的范围搜索功能?答案是肯定的。观察上图,二叉树的叶子节点是按顺序排列的,从左到右按升序排列,如果我们需要查找id>5的数据,那么我们可以取出节点为6的节点及其右边的节点subtree,范围搜索比较容易实现。但是普通的二叉查找树有一个致命的缺陷:极端情况下会退化为线性链表,二分查找也会退化为遍历查找。时间复杂度退化为O(N),检索性能急剧下降。比如下面这种情况,二叉树极度不平衡,已经退化为链表,检索速度大大降低。此时,检索id=7的数据所需的计算次数变成了7次。在数据库中,数据的自增是一种很常见的形式。比如一张表的主键是id,主键一般默认是自增的。如果使用二叉树等数据结构作为索引,必然会出现上述不平衡状态导致的线性搜索问题。因此,简单二叉搜索树存在不平衡导致检索性能低下的问题,不能直接用于底层Mysql索引的实现。3.AVL树和红黑树二叉搜索树存在不平衡问题,因此学者提出通过树节点的自动轮换和调整,使二叉树处于基本平衡状态,以保持二叉搜索的最佳搜索性能树起来。基于这种思想的自调整平衡状态的二叉树包括AVL树和红黑树。首先简单介绍一下红黑树,它是一种自动调整树形的树结构。例如,当二叉树处于不平衡状态时,红黑树会自动旋转左手和右手节点并改变节点的颜色来调整树的形状。它保持了一个基本的平衡(时间复杂度为O(logn)),保证了搜索效率不会明显降低。比如从1到7按升序插入数据节点,如果是普通的二叉搜索树,会退化成链表,但红黑树会不断调整树的形状,使其保持在一个链表中基本平衡状态,如下图所示。在下面的红黑树中找到id=7的比较节点个数为4个,依然保持了二叉树良好的查找效率。红黑树的平均搜索效率很好,不会出现极端O(n)的情况。有没有可能实现红黑树作为Mysql的底层索引?其实红黑树也存在一些问题。观察下面的例子。红黑树顺序插入1~7个节点,搜索id=7时需要计算的节点个数为4。红黑树顺序插入1~16个节点,需要计算的节点个数相比findid=16是6倍。看看这棵树的形状。是不是顺序插入数据的时候,树的形状一直是“右倾”的趋势?从根本上说,红黑树并没有完全解决二叉搜索树。虽然这种“右倾”的趋势远没有二叉搜索树退化成线性链表那么夸张,但是数据库中基本的主键自增操作,主键一般都是几百万几千万,如果红色-blacktree有这个问题,同样会消耗巨大的搜索性能。我们的数据库不可能忍受这种无意义的等待。现在考虑另一种更严格的自平衡二叉树,即AVL树。因为AVL树是绝对平衡的二叉树,所以在调整二叉树的形状上会消耗更多的性能。AVL树顺序插入1~7个节点,比较节点找到id=7的次数为3次AVL树顺序插入1~16个节点,查找id时需要比较的节点个数=16就是4,从查找效率上来说,AVL树查找的速度要高于红黑树(AVL树是4次比较,红黑树是6次比较)。从树的形状来看,AVL树不存在红黑树的“右倾”问题。也就是说,大量的顺序插入不会导致查询性能下降,从根本上解决了红黑树的问题。总结一下AVL树的优点:搜索性能好(O(logn)),不存在极度低效的搜索。可以实现范围搜索和数据排序。看来AVL树作为数据查找的数据结构确实不错,但是AVL树不适合做Mysql数据库的索引数据结构,因为考虑这个问题:数据库查询数据的瓶颈在磁盘IO。如果用一棵AVL树,我们每个树节点只存储一份数据,我们一次磁盘IO只能把一个节点上的数据取出来加载到内存中。比如查询id=7的数据,需要进行3次磁盘IO。这是多么耗时。因此,我们在设计数据库索引时,首先需要考虑如何尽可能减少磁盘IO次数。磁盘IO有一个特点,就是从磁盘读取1B数据和1KB数据消耗的时间基本是一样的,我们可以按照这个思路,我们可以在一个树节点上存储尽可能多的数据,一次磁盘IO加载更多的数据放入内存,这是B-tree和B+树的设计原则。4.B-tree下面的B-tree中,每个节点限制最多存储两个key,如果一个节点有两个以上的key,它会自动分裂。例如下面的B树存储了7条数据。只需要查询两个节点就可以知道id=7的数据的具体位置,也就是经过两次磁盘IO就可以查询到指定的数据,比AVL树要好。下面是一个B树,存储了16条数据。同样,每个节点最多可以存储2个密钥。查询id=16的数据,需要查询比较4个节点,也就是经过4次磁盘IO。看起来查询性能与AVL树相同。但是考虑到磁盘IO读取1条数据消耗的时间和读取100条数据基本一样,那么我们的优化思路可以改为:在一次磁盘IO中尽可能多的读取数据到内存中。这直接体现在树的结构上,即可以适当增加每个节点可以存储的key。当我们将单个节点限制的key个数设置为6时,一个存储了7条数据的B-tree,查询id=7的数据需要2次磁盘IO。一个存储16条数据的B-tree需要2次磁盘IO才能查询到id=7的数据。与AVL树相比,磁盘IO的数量减少了一半。因此,在数据库索引数据结构的选择上,B树是一个非常好的选择。综上所述,B树作为数据库索引有以下优点:极好的检索速度和时间复杂度:B树的搜索性能等于O(h*logn),其中h为树高,n为每个节点中的关键字数量。数字;尽可能少的磁盘IO,加快检索速度;可以支持范围搜索。5、B+树和B+树有什么区别?首先,B树的一个节点存储数据,而B+树存储索引(地址),所以B树的一个节点不能存储很多数据,但是B+树的一个节点可以存储很多索引,并且B+树的叶子节点保存所有数据。第二,B+树的叶子节点在数据阶段用一个链表串联起来,方便范围查找。通过B-tree和B+树的对比,我们可以看出B+树节点存储的是索引。当单个节点的存储容量有限时,单个节点还可以存储大量的索引,从而降低了整个B+树的高度,减少了磁盘IO。其次,B+树的叶子节点才是真正存储数据的地方。叶节点通过链表连接。链表本身是有序的,在查找数据范围时效率更高。所以Mysql的索引使用的是B+树,在搜索效率和范围搜索上都有非常好的表现。Innodb引擎和Myisam引擎的实现Mysql的底层数据引擎采用插件的形式设计。最常见的是Innodb引擎和Myisam引擎。用户可以根据个人需求选择不同的引擎作为Mysql数据表的底层引擎。刚才我们分析了B+树作为Mysql的索引的数据结构是非常合适的,但是如何组织数据和索引也需要一些设计。不同的设计理念也导致了Innodb和Myisam的出现,各自展现出独特的性能。MyISAM虽然有优秀的数据查找性能,但是不支持事务处理。Innodb最大的特点就是支持兼容ACID的事务函数,并且支持行级锁。Mysql创建表时,可以指定引擎。例如,在下面的例子中,分别指定Myisam和Innodb作为user表和user2表的数据引擎。执行这两条命令后,系统中出现如下文件,说明这两种引擎的数据和索引组织方式不同。Innodb建表后生成的文件为:frm:建表语句idb:表中数据+索引文件Myisam建表后生成的文件为frm:建表语句MYD:数据文件inthetable(myisamdata)MYI:表中的索引文件(myisamindex)从生成的文件可以看出,两个引擎的底层数据和索引组织不同。MyISAM引擎将数据和索引分离,一人一个文件,称为非聚集索引方式;Innodb引擎将数据和索引放在同一个文件中,称为聚集索引方式。下面将从底层实现的角度来分析这两个引擎是如何依赖B+树的数据结构来组织引擎实现的。MyISAM引擎底层实现(非聚集索引方式)MyISAM采用非聚集索引方式,即数据和索引落在两个不同的文件上。MyISAM建表时,以主键作为KEY构建主索引B+树,树的叶子节点存放对应数据的物理地址。我们得到物理地址后,就可以直接在MyISAM数据文件中定位到具体的数据记录。当我们为一个字段添加索引的时候,我们也会为对应的字段生成一个索引树。该字段的索引树的叶子节点也记录了对应数据的物理地址,然后利用这个物理地址在数据文件中定位到具体的数据记录。2.Innodb引擎底层实现(聚簇索引方式)InnoDB是聚簇索引方式,所以数据和索引存储在同一个文件中。首先,InnoDB会以主键ID为KEY构建索引B+树,如下左图所示,B+树的叶子节点存储主键ID对应的数据。例如,执行select*fromuser_infowhereid=15语句时,InnoDB会查询主键ID索引B+树,找到对应的user_name='Bob'。这就是InnoDB在建表时会自动创建主键ID索引树的原因,这也是Mysql建表时要求指定主键的原因。当我们对表中的字段进行索引时,InnoDB是如何构建索引树的?比如我们要给user_name字段加一个索引,那么InnoDB会构建一个user_name索引B+树,节点存储user_nameKEY,叶子节点存储的数据就是主键KEY。注意叶子存放的是主键KEY!得到主键KEY后,InnoDB会根据刚刚在user_name索引树中找到的主键KEY,去主键索引树中查找对应的数据。问题来了,为什么InnoDB只在主键索引树的叶子节点存储具体的数据,而其他索引树却不存储具体的数据,没必要先找主键,再在里面找对应的数据主键索引树?其实很简单,因为InnoDB需要节省存储空间。一张表中可能有很多索引,InnoDB会为每个索引字段生成一个索引树。如果每个字段的索引树都存储了具体的数据,那么这张表的索引数据文件就会变得非常庞大(数据极度冗余)。从节省磁盘空间的角度来看,确实没有必要在每个字段索引树中存储具体的数据。通过这个看似“压倒性”的步骤,以降低查询性能为代价节省了大量的磁盘空间。这是非常值得的。在比较InnoDB和MyISAM的特性时,提到MyISAM具有更好的查询性能。从上面索引文件数据文件的设计也可以看出原因:MyISAM直接找到物理地址后就可以直接定位到数据记录,但是InnoDB查询到叶子节点后??,需要查询主键索引树再次定位具体数据。相当于MyISAM一步找到数据,而InnoDB需要两步,所以MyISAM当然查询性能更高。本文首先讨论了哪种数据结构更适合作为Mysql底层索引的实现,然后介绍了Mysql的两大经典数据引擎MyISAM和InnoDB的底层实现。最后总结一下什么时候需要给表中的字段加索引:经常作为查询条件的字段应该加索引;唯一性差的字段不适合单独创建索引,即使该字段经常被用作查询条件;更新非常频繁的字段不适合索引。
