数据库的架构部分:
这部分主要是考察大家对数据库有没有个整体的了解,
此外呢最重点最常考的有两个,就是索引部分,还有锁部分以及一些实用的工作中用到的语法部分。
最后还有数据库存理论部分,如范式一的列不可再分,范式二的要有主见,范式三的,就是去除传递依赖等等,这个理论范式部
分呢就需要大家课下去了解,明确了考点之后,我们接下来要将其逐一攻克。
咱们首先来看看一个容易让同学懵掉的题目,如何设计一个关系型数据库?
遇到这类问题往往容易让人不知所措,因为要说的东西太多了,其实不必慌张。万变不离其宗,涉及一个数据库,就跟设计一款
软件差不多,也是将其分解成很多模块。
主要是考察一下我们的模块划分能力以及对数据库的了解,记一个东西的最佳方式便是站在设计者的角度去设计它,所以在我们
能回答出这个问题,也说明展对数据库的基本功能以及作用有了一个全面的了解。
解题之前,咱们先来具体分析一下数据库的各个模块,以便加深对数据库的认识。设计及模块划分。
那要开发一个数据库,咱们需要什么模块?
首先数据库的最主要功能是什么?
就是存储我们的数据,因此它会有一个存储模块来负责存储我们的数据,存储模块就类似我们的OS文件系统将数据最终持久化
存入磁盘中,如存入这个机械硬盘或者SSD固态硬盘一或者是他们的磁盘阵列矩阵中,可是光有存储是不行的,我们还需要组织
并且用到这些数据,因此咱们需要有这个程序的实力,用逻辑结构来映射出物
理结构来,并且在程序中提供获取以及管理数据的方式,还有必要的这个问题追踪机制。
接下来咱们来细分一下程序的模块:
首先我们需要对数据的格式以及文件的分隔进行统一的管理,即把物理数据通过逻辑的形式给组织和表示出来,于是便涉及到了
咱们程序的存储管理模块,那这里插入个题外话,我们需要如何优化存储效能,咱们都知道处理数据呢不可能在磁盘上做,肯定
是让程序呢加载到程序空间所在的内存里去做,而磁盘IO速率呢往往是程序执行速度的主要瓶颈,大家可以看到咱们的机械硬盘
啊就是它通过马达的驱动,盘面的转动,以及磁头的移动来查找数据,这样的速度呢远差于内存的查找效率,即便是通过电流驱
动的这个固态硬盘,虽然拥有比机械硬盘更快的IO速度,但是跟内存比起来呢也不是同一个量级的,因此为了执行效率,咱们呢
要尽可能的减少IO。就这个存储管理功能而言,咱们如果按照逐行去查找并返回,那么这频繁IO呢会使数据库的执行效率慢如蜗
牛,因为一次IO读取多条数据和单条数据呢花费的时间并没有多大的区别,所以呢我们可以一次性的去读取多行以提升IO的效
能,所以行就失去了它的意义。那实际上呢数据库也是把逻辑存储单位呢用块或者页来表示,每个块或者页中会存放多行数据,
这样读取的时候后呢可以将多个块呢一起加来进内存当中。
作为一款很追求性能的软件,我们没有好好利用内存,因此呢为了更快更好的优化我们的程序,我们应该想到做项目的时候呢被一种普遍做法便是引入缓存机制。
把取出来的数据块呢?存放在缓存里,下次需要的时候直接从内存返回而不用发生IO这里需要补充的是!刚刚咱们说到的一次性
加载多个模块或者页,这些块里呢包含的数据行有相当的一部分并不是我们本次查询需要的行,但是呢根据一旦某行数据被访问
了,那么它周围的数据也极有可能被访问的经验,咱们缓存的非本次数据呢也能起到优化访问效率的作用,那个块里面呢一次加
进来的这个快中它有abc3条数据,我们只需要A的数据吗?那B和C呢它也被加载到内存当中了,那按照经验来说呢下次B和C呢
被访问到的这个几率呢是非常大的,那也就是他也能提升了我们的访问的性能。关于如何管理缓存呢有很多的方法,如
要LRU等也不能说哪个方法最好某种方法适用于某个具体的场景,大家感兴趣也可以去了解一下,
回归正题,接下来我们还要能提供给外界指令来操作我们的数据库即可读的SQL语言,那么呢我们就需要一个SQL的解析模块,
将SQL编译解析转换成机器可识别的指令,那这个时候为了进一步去提升SQL执行效率该怎么办?老办法还是将我们的SQL呢缓
存到咱们的缓存里,那编译好的sql呢?方便下次来了直接就进行解析就可以了,大家可以注意到我们设计程序的时候呢要先实现
功能,然后再接着去考虑优化周而复始!这里还需要补充一点,我们的缓存呢不宜过大,且算法里呢得有淘汰机制,淘汰掉一些
之后不常用的数据。
此外咱们做的这个sql操作需要记录下来,方便我们做数据库的主从同步或者灾难恢复,因此我们就需要有日志管理办法呢去对我
们的操作记录做记录啊,大家可以了解一下这个binlog的记录方式,面试的时候我会问到,
再者,我们还需要提供给用户管理数据的私密空间及权限划分,通俗的讲就是让老板能看到员工的数据,而员工呢只能看到自己
该看到的数据,权限划分是我们dbA做的事情,通常不需要我们过多的关心,那设计系统的时候,除了考虑正常的功能,还得考
虑异常的情况,那怎么处理异常的情况呢,我们就需要引入异常机制。就是容灾。当我们数据库挂了如何恢复?恢复到什么程
度,这些都需要设计,OK接下来为了进一步提升查询数据的速度以及让数据库呢支持并发,咱们还需要引入最能够突出数据库
特点的两个模块,索引还有锁模块,这两个模块呢既是我们接下来讲解的重点,也是数据库面试的重中之重OK
那么如何来回答刚刚的问题是不是简单多了,要涉及一个关系型数据库,咱们首先要将其划分成两大部分,
一个是存储部分,该部分类似一个文件系统来将数据持久化到存储设备当中,那光有存储是不行的,咱们还需要有程序实例模块来对存储进行逻辑上的管理。
而程序实例部分呢:
- 将包含数据的逻辑关系转化成物理存储关系的存储管理模块。
- 优化执行效率的缓存模块。
- 将SQL语句进行解析的sql解析模块。
- 记录操作的日志管理模块。
- 进行多用户管理的权限划分模块。
- 灾难恢复模块。
- 优化数据查询效率的索引模块。
- 以及使得数据库支持并发操作的锁模块,
程序实例主要划分为这八个模块。
那现在回答这个问题是不是变得非常轻松了?讲到这里,不知大家是否会体会到,数据库的开发其实跟我们做的项目一样,考虑设计的模块呢也都非常类似的,其架构呢堪称经典,所以对我们的程序开发以及以及其呢很有借鉴的意义。
接下来来到面试的重点模块,所以咱们将通过解决一些面试里常见的问题,来形成对索引的一个比较完整的知识体系,那么问题如下,
第一,为什么要使用索引?
第二,什么样的信息能成为索引?
第三,索引的数据结构?
第四,密集索引和稀疏索引的区别?
咱们先来解决第一个问题。这个问题比较宽泛,就是为什么要使用索引?所以要回答这个问题呢?咱们先来对查询数据的方式做
个调研,我们先试试用最简单的方式来实现数据查询,它便是全表扫描,即将整张表的数据全部或者分批次加载到内存当中,刚
刚我们说到存储的最小单位是块或者页,他们是由多行数据来组成的,然后呢,我们将这些块呢,都加了进来,然后逐个块去轮
巡,找到我们要的目标并返回。这种方式普遍被认为是会非常的慢的,那他在所有的情况下都这么慢吗?实际上存在即合理,它
也有很适用的地方,当你只有少量的数据比如说十几十行左右,那么直接加载到内存里进行全表扫描呢肯定比我们后面要说的这
种通过索引查询的方式要快,当然要在数据量很大的表里进行查询的话呢,该方法显然不适用了,因此这里就引出了我们答案,
很多情况下我们都要避免全表扫描的情况发生,所以咱们的数据库呢得引入一种更高效的机制,这便是索引了,他的灵感来自于
字典,那在字典里面呢只要我们把一些关键信息组织起来,比如说偏旁部首这些查询的时候,依据这些信息的指引呢就能够查到
我们想要的页面,很快便能定位到我们要查的字了,而这些关键信息以及查找数据的方式便组成了我们的索引,通过索引来大幅
提升查询速度便是问题的答案。当然这样刚刚呢去回答并不会令面试官满意,因此你需要听听接下来的是理解后用自己的方式去
作答,这样别人才会觉得你对这块是真正的了解了,大致回答完上一个问题,
咱们借此进一步深入!我们来看看第二个问题及数据记录中有什么信息能成为索引?
我们通过上面的内容呢可想而知,自然是能把该记录限定在一定查找范围内的字段就是我们刚才说的关键信息嘛,我们的主键便
是一个很好的索引切入点。当然其他键位包括唯一键,普通键等也可以作为索引,
现在有了关键字还不行,关键是需要让他以什么样的逻辑结构组织起来才能让我们检索的更高效集我们现在来分析,索引的数据
结构,咱们这里就要想一些让查询变来高效的数据结构了,最简单的便是二叉查找树,复杂点呢是它的变种,平衡二叉树,红黑
树以及B-Tree还有B+树,咱们的mysql数据库索引最终主要是通过B+Tree来实现的,就是第三个B+树。那当然呢还有第四个一些
这个哈希结构,那为了使同学对索引的印象更为深刻呢,接下来咱们将各种数据结构用在我们的索引上,以体验其优势和劣势之
后,在更为详细地回答这一个问题。
优化你的索引-运用二叉查找树
二叉查找树是每个结点最多有两个子树的树结构。通常子树被称作左子树或者右子树。
二叉查找树的重要性质是对于树中的每一个结点X这里就是以根节点五来做比方,他的左子树的任意节点的值均小于X2小于五,
同时右子树上的任意节点的值均大于X也就是大于五。
如果用二叉查找树来作为我们的索引,确实能够提升查询效率。这里需要大家注意的是,我们说的索引的存储块和我们之前说的
数据库的最小存储单位,块或者页实际上并非一一对应,只是为了方便我们的理解,先将其一一对应起来了。每个存储块存储的
是关键字,还有指向指数的自身。像这棵树它不仅仅是二叉树,还是平衡二叉树。
什么是平衡二叉树呢?就是它的任意一个节点的左子树,它的高度均不超过一。
这里我们从根部开始,根部的左子树比根部的右子树,它的高度要差一。右子树比较高一些。到底部了,左右子树的高度是一样
的,总之它的高度是不能超过一的。二叉查找,树的查找用的是二分查找。比如说我们要搜六,这里六要比五要大,因此我们就
要从右孩子去查找。右孩子当我们就来到了七,七呢它会比六大,因此我们就会去查七得左孩子。这样子我们就能定位到六了。
因为是对半搜索,所以它的时间复杂度是O(logN)因此其查询效率是非常高的,但是它也有缺点。
首先咱们的数据库的数据可能面临着增加和删除。我们假定一下,在这里我们经过数据的删除了之后,节点2,还有节点六都被
删除了。同时我们新增了关键词为11的节点,根据二叉查找树的特性,它会被插入到9的关键字的右边右子树当中。这里呢插入
了一个11,紧接着我们又插入了13,要根据平衡二叉树的插入原则,我们就会插入到11的右子树当中。13他最终就会演变成这样
子,就变成了一个线性的二叉树了。
那么它的查询时间复杂度就变成了O(N)大大降低了查询效率。有的人会说我们就可以利用树的一个旋转的特性来保持这棵
树是平衡二叉树,这样其时间复杂度会维持在O(logN)就OK了。这样确实解决了第一个问题。
但是它还会有第二个问题。咱们之前说了影响程序运行速度的瓶颈在哪里?对了,就是IO。如果我们假定这些索引块都在磁盘
中,拿刚刚的检索来讲,去找6会先发生一次IO,将这个根的数据都入到我们的内存当中,之后再发生IO将其读入进来,紧接着
又发生一次IO,读了六即检索深度,每增加一就会发生一次IO,咱们的平衡二叉树也好,红黑树也罢,每个节点最多只能有两个
孩子,而咱们的数据块会非常的多,因此为了组织起这些数据块,咱们的树的深度就会很深很深,IO的次数也会很多。这样数据
一多,其检索性能比咱们之前说的全表扫描要慢很多,根本就没法满足我们的优化查询的需求了。
咱们还有什么办法能够既降低查询的时间,复杂度又降低IO的次数,主要就是让树变矮一些,数据每个节点能成熟的数据多一些,这个时候咱们就想到BTree了。
优化你的索引-运用B树
平衡多路查找树。那如果每个节点最多有M个孩子,那么这样的树就是M阶B树。咱们可以看到该图便是一个三阶B树的样子。当
然现实中呢讲的索引每个节点的孩子树上限了肯定是远大于三的。每个存储块中主要包含了关键字和指向孩子的指针,那么最多
能有几个孩子取决于每个存储块的容量以及数据库的相关配置。所以通常情况下这个M是很大的。OK咱们首先来看看Btree的特征。
第一,根节点至少包括两个孩子,这个没什么好解释的,比较容易理解。
第二呢,树中每个节点最多含有M个孩子,那这个M是大于或者等于2的。这个就是咱们说的M阶数的含义了。M取决于节点的容量以及相关配置。
那么第三呢,除根节点和叶节点外,其他每个节点至少有ceil M除以二个孩子,那其中这个cell呢就这个函数呢表示取上限。
比如说像这里,我们刚刚的这个M为三,对吧?那么M除以二等于1.5,那我们取它的上限,也就是cell的上限呢,它就是二,它
并不是四舍五入啊,比如说等于1.2,它也是取得上线。那每个非跟非叶子节点呢至少具备两个孩子,
就跟咱们看到的这个三阶B树一样,就跟这个三阶B树一样。
那我们就来看看第四个。
第四个定义呢就是所有叶子节点呢都位于同一层级,叶子节点的高度呢都是一样的。
那咱们通过这个数来回顾一下我们的这四个特征。
第一它的根节点呢至少有两个孩子。
那第二,每个节点最多包含根阶数个孩子。这里我们是三阶嘛,所以呢他的孩子数呢不能大于三,并且呢孩子数呢也不能少于
二,是不能少于两个的哈、不管你是几阶树,孩子数并不能少于二。那同时呢,对于非根,非叶子节点来讲呢,它上面所具备的
这个孩子数呢至少为M除以二的上限。此外呢所有的终端叶子节点呢都位于同一层,那这里呢与其说是它的特征,不如说是他必
须遵守的一个约束。遵守这样的约束,目的只有一个,就是让每个索引派尽可能存储更多的信息,让树的高度尽可能减少IO次
数。那关于这点呢大家可以细细去体会。咱们再来看看它的最后一条约束。最后一个约束其实有三个子约束来组成,假设每个非
终端节点中包含有N个关键字信息,那么我们看第一条就是我们假设KI为这个非终端节点的这个关键字嘛,I呢是等于1或者到N,
则我们的这个关键字就需要按照顺序升序进行排序。
也就是说Ki减1呢必须小于KI,就拿咱们的这个图来讲,比如说我们这个KI这里呢是K2,K2呢是12,那Ki减一呢就是K1,K呢是
8?那8那必须得比12小的吧,是升序排列的。OK,
那咱们就来看看第二条。关键字的个数N呢必须满足这个ceil M除以2-1,小于或等于N,N呢要小于或等于M减一。集说的是任意节点的关键字个数上限呢比他的孩子树上限少一个。且对于非叶子节点来说呢,任何一个节点的关键字个数比它的指向孩子的指针个数少一个。
我们可以看看上图,就是这里咱们的这个关键字的个数呢永远是比这个孩子的数少一个。OK咱们再来看看第三条,非叶子节点
的指针P1,P2到PM其中P1指向关键字小于K1的子数,什么意思呢?他就说的是某节点最左边的孩子节点里的关键字的值呢均小
于该结点最左边的关键字的值。咱们可以看看图,图里面呢我们拿这个8来做例子。8是关键词哈,那它8对应它左边的这个子树
里面的他的孩子里面的关键字的值呢三和五呢,它都会小于8就是这么个意思。
咱们再来看看另外一条,就是pm指向关键字。大于KM减一的指数,这个什么意思呢?就指的是该结点,最右边的这个孩子节点
里的关键字的值呢,均大于该节点里所有关键字的值。什么意思呢?我们来看一看图。就比如说这个节点里面,它的最右边的这
个孩子13和15,它这两个字呢均大于这个节点里所有的关键字的值。八和12嘛?十三和十五,那明显大于八和12。
咱们再来看看最后一条,其他PI呢指向关键字属于就要大于k【i-1】小于k【i】的这个关键字的区间范围的指数,它是一个开区间
的,那什么意思呢?
就是该节点其余的孩子节点里的关键字的值的大小,均位于离该孩子结点指针最近的两个关键词之间,它是一个开区间了。咱们
可以看看,就拿这个P2为例啊,P2它指向的这两个关键词呢,九和十呢是位于八和12的开区间范围之内的,它就要表达这么一
个意思,所以呢,它的最左边和最右边以及中间的这些规定呢,我们也已经定好了。咱们可以看到B树的前四条规则呢主要是用
来限定这个B数的孩子数,每个节点的孩子数,还有这个B树的深度的。
而它的最后一条规定呢则是用来限定B树结点关键字数量以及大小了。当我们要查找数据的时候,这个B树呢他跟二叉查找树的产
生效率呢是一样高效的。比如说咱们要查这个15,15在这里哈,那我们首先先从根部开始找,15比17要小,因此呢他就来到这
个p1里面,P指向的是这个指数。那这个节点里面呢我们由于这个15是比八和12都要大的,因此呢他就来到这个P3,P3呢就指
向这个叶子节点了。13跟15在一起的,我们先找13,不是那在最终找到了15,那因此呢他的这个查找效率呢同样是O(logn)。
我们呢当数据发生变动的时候呢,必然会存在现有结构被打乱的情况。那我们的这个二叉查找树呢它就会有可能被打完成线性
的。那由于B树我们会有这五个规定存在,所以呢B树就会有相应的策略,通过这个合并分裂上一下一节点,来保持它的这个特
征,时速远比我们的二叉树矮得多,并且不会经过数据不断变动后变成线性的这种情况。由于我们重点讲索引,所谓具体的这些
策略呢以及它的实现了,大家可以去了解一下。面试的时候,这类数据结构的实现,有时候也会作为面试官的利器来考大家。