1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > SQL必知必会-进阶篇[SQL学习笔记]

SQL必知必会-进阶篇[SQL学习笔记]

时间:2018-09-01 14:11:23

相关推荐

SQL必知必会-进阶篇[SQL学习笔记]

本篇博客是对于陈旸老师极客专栏“SQL 必知必会”进阶篇的笔记总结。需要学习资料可私信。

文章目录

第20课数据库优化 第21课数据库的设计范式都有哪些?数据表的键都有哪些?1NF、2NF 和 3NF 指的是什么?1NF2NF3NF 需要注意的事项 第22课3NF 有什么不足?除了 3NF,我们为什么还需要 BCNF?有了范式设计,为什么有时候需要进行反范式设计?反范式设计适用的场景是什么?又可能存在哪些问题?数据库和数据仓库的区别 第23课什么情况下创建索引,什么时候不需要索引?索引的种类有哪些? 第24课为什么索引要存放到硬盘上?如何评价索引的数据结构设计的好坏?使用平衡二叉树作为索引的数据结构有哪些不足?B 树和 B+ 树的结构是怎样的?为什么我们常用 B+ 树作为索引的数据结构? 第25课了解 MySQL 中的 Hash 索引,理解使用它的优点和不足。Hash 索引和 B+ 树索引的区别以及使用场景。 第26课什么情况下使用索引?当我们进行数据表查询的时候,都有哪些特征需要我们创建索引?索引不是万能的,索引设计的不合理可能会阻碍数据库和业务处理的性能。那么什么情况下不需要创建索引?创建了索引不一定代表一定用得上,甚至在有些情况下索引会失效。哪些情况下,索引会失效呢?又该如何避免这一情况? 第27课数据库中的存储结构是怎样的?页、区、段和表空间分别指的是什么?为什么页(Page)是数据库存储空间的基本单位?从数据页的角度来看,B+ 树是如何进行查询的? 第28课数据库的缓冲池在数据库中起到了怎样的作用?如果我们对缓冲池内的数据进行更新,数据会直接更新到磁盘上吗?对数据页进行加载都有哪些方式呢?(一些操作执行可以再补充) 第29课(不是很懂)(待补充)什么是索引片?如何计算过滤因子?设计索引的时候,可以遵循哪些原则呢?为什么理想的索引很难在实际工作中应用起来? 小总结第30课就分类而言,锁的划分有多种方式,这些划分方式都包括哪些?(操作指令后面再补充)乐观锁和悲观锁的思想是什么?乐观锁有两种实现方式,这两种实现方式是什么?多个事务并发,发生死锁时该如何解决?怎样降低死锁发生的概率? 第31课MVCC 机制的思想是什么?为什么 RDBMS 会采用 MVCC 机制?在 InnoDB 中,MVCC 机制是如何实现的 ?Read View 是如何工作的? 第32课什么是查询优化器?一条 SQL 语句的执行流程都会经历哪些环节,在查询优化器中都包括了哪些部分?查询优化器的两种优化方式分别是什么?基于代价的优化器是如何统计代价的?总的代价又如何计算?(不全)

第20课

数据库优化

需要思考的几个问题

数据库调优的目标(目的)如何确定调优的目标如何进行数据库调优(三个方面)

第21课

数据库的设计范式都有哪些?

6 种范式,按照范式级别,从低到高分别是:1NF(第一范式)、2NF(第二范式)、3NF(第三范式)、BCNF(巴斯 -科德范式)、4NF(第四范式)和 5NF(第五范式,又叫做完美范式)。

数据表的键都有哪些?

超键:能唯一标识元组的属性集叫做超键。

候选键:如果超键不包括多余的属性,那么这个超键就是候选键。

主键:用户可以从候选键中选择一个作为主键。

外键:如果数据表 R1 中的某属性集不是 R1 的主键,而是另一个数据表 R2 的主键,那么这个属性集就是数据表 R1 的外键。

主属性:包含在任一候选键中的属性称为主属性。

非主属性:与主属性相对,指的是不包含在任何一个候选键中的属性。

我们也将候选键称之为“码”,把主键也称为“主码”。因为键可能是由多个属性组成的,针对单个属性,我们还可以用主属性和非主属性来进行区分。

举个例子

球员表(player):球员编号、姓名、身份证号、年龄和球队编号球队表:球队编号、主教练和球队所在地。超键就是包括球员编号或者身份证号的任意组合,比如(球员编号)(球员编号,姓名)(身份证号,年龄)等。候选键就是最小的超键,对于球员表来说,候选键就是(球员编号)或者(身份证号)。主键是我们自己选定,也就是从候选键中选择一个,比如(球员编号)外键就是球员表中的球队编号。主属性是(球员编号)(身份证号),其他的属性(姓名)(年龄)(球队编号)都是非主属性。

1NF、2NF 和 3NF 指的是什么?

参考博客!!!!!!!!!!!!

1NF

第一范式:符合1NF的关系中的每个属性都不可再分,如下的就不符合 第一范式存在的几个问题,如下图,具体描述参考链接数据冗余过大插入异常删除异常修改异常

2NF

第二范式:2NF 指的数据表里的非主属性都要和这个数据表的候选键(也叫做码)有完全依赖关系其实需要理解这个概念只需要知道2个概念完全函数依赖部分函数依赖,举个例子如下完全函数依赖: 学号 F→ 姓名 or (学号,课名) F→ 分数部分函数依赖:(学号,课名) P→ 姓名还有一个传递函数依赖也需要知道 还有一个需要掌握的就是如何判断是否符合2NF 第一步:找出数据表中所有的码。第二步:根据第一步所得到的码,找出所有的主属性。第三步:数据表中,除去所有的主属性,剩下的就都是非主属性了。第四步:查看是否存在非主属性对码的部分函数依赖 为了消除这些部分函数依赖,只有一个办法,就是将大数据表拆分成两个或者更多个更小的数据表,但是方法并不唯一第二范式也是存在问题的,具体参考链接

3NF

第三范式:3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖。也就是说, 如果存在非主属性对于码的传递函数依赖,则不符合3NF的要求。举个例子:,主码为学号,主属性为学号,非主属性为姓名、系名和系主任。因为 学号 → 系名,同时 系名 → 系主任,所以存在非主属性系主任对于码学号的传递函数依赖,所以学生表的设计,不符合3NF的要求。

需要注意的事项

范式只是提出了设计的标准,实际上设计数据表时,未必要符合这些原则。一方面是因为这些范式本身存在一些问题,可能会带来插入,更新,删除等的异常情况(这些会在下一讲举例说明),另一方面,它们也可能降低会查询的效率。这是为什么呢?因为范式等级越高,设计出来的数据表就越多,进行数据查询的时候就可能需要关联多张表,从而影响查询效率。

第22课

3NF 有什么不足?除了 3NF,我们为什么还需要 BCNF?

从上述表中我们可以得到以下结论

仓库名决定了管理员,管理员也决定了仓库名,同时(仓库名,物品名)的属性集合可以决定数量这个属性。

找到数据表的候选键是(管理员,物品名)和(仓库名,物品名)

候选键中选择一个作为主键,比如(仓库名,物品名)。

主属性是包含在任一候选键中的属性,也就是仓库名,管理员和物品名。非主属性是数量这个属性。

符合 1NF的要求;其次,数据表中非主属性”数量“都与候选键全部依赖,(仓库名,物品名)决定数量,(管理员,物品名)决定数量,因此,数据表符合 2NF的要求;最后,数据表中的非主属性,不传递依赖于候选键。因此符合 3NF的要求。

但是依然存在问题如下

增加一个仓库,但是还没有存放任何物品。根据数据表实体完 整性的要求,主键不能有空值,因此会出现插入异常;如果仓库更换了管理员,我们就可能会修改数据表中的多条记录;如果仓库里的商品都卖空了,那么此时仓库名称和相应的管理员名称也会随之被删除。

人们在 3NF 的基础上进行了改进,提出了BCNF,也叫做巴斯 - 科德范式,它在 3NF 的基础上消除了主属性对候选键的部分依赖或者传递依赖关系。通过拆分表能达到要求

有了范式设计,为什么有时候需要进行反范式设计?

尽管围绕着数据表的设计有很多范式,但事实上,我们在设计数据表的时候却不一定要参照这些标准。

我们在之前已经了解了越高阶的范式得到的数据表越多,数据冗余度越低。但有时候,我们在设计数据表的时候,还需要为了性能和读取效率违反范式化的原则。反范式就是相对范式化而言的,换句话说,就是允许少量的冗余,通过空间来换时间

如果我们想对查询效率进行优化,有时候反范式优化也是一种优化思路。

反范式设计适用的场景是什么?又可能存在哪些问题?

在数据量小的情况下,反范式不能体现性能的优势,可能还会让数据库的设计更加复杂。比如采用存储过程来支持数据的更新、删除等额外操作,很容易增加系统的维护成本。

比如用户每次更改昵称的时候,都需要执行存储过程来更新,如果昵称更改频繁,会非常消耗系统资源。

在现实生活中,我们经常需要一些冗余信息,比如订单中的收货人信息,包括姓名、电话和地址等。每次发生的订单收货信息都属于历史快照,需要进行保存,但用户可以随时修改自己的信息,这时保存这些冗余信息是非常有必要的。

当冗余信息有价值或者能大幅度提高查询效率的时候,我们就可以采取反范式的优化

数据库和数据仓库的区别

数据库设计的目的在于捕获数据,而数据仓库设计的目的在于分析数据;数据库对数据的增删改实时性要求强,需要存储在线的用户数据,而数据仓库存储的一般是历史数据;数据库设计需要尽量避免冗余,但为了提高查询效率也允许一定的冗余度,而数据仓库在设计上更偏向采用反范式设计。

第23课

什么情况下创建索引,什么时候不需要索引?

什么是索引

数据库中的索引,就好比一本书的目录,它可以帮我们快速进行特定值的定位与查找,从而加快数据查询的效率。

索引就是帮助数据库管理系统高效获取数据的数据结构。

必须知道索引不是万能的,在有些情况下使用索引反而会让效率变低。举个例子 在数据表中的数据行数比较少的情况下,比如不到 1000 行,是不需要创建索引的。另外,当数据重复度大,比如高于 10% 的时候,也不需要对这个字段使用索引

索引的种类有哪些?

索引主要有 4 种,分别是普通索引、唯一索引、主键索引和全文索引。

普通索引是基础的索引,没有任何约束,主要用于提高查询效率。

唯一索引就是在普通索引的基础上增加了数据唯一性的约束,在一张数据表里可以有多个唯一索引

主键索引在唯一索引的基础上增加了不为空的约束,也就是 NOT NULL+UNIQUE,一张表里最多只有一个主键索引

全文索引用的不多,MySQL 自带的全文索引只支持英文。我们通常可以采用专门的全文搜索引擎,比如 ES(ElasticSearch) 和 Solr。

其实前三种索引(普通索引、唯一索引和主键索引)都是一类索引,只不过对数据的约束性逐渐提升、

按照物理实现方式,索引可以分为 2 种:聚集索引和非聚集索引(二级索引)

聚集索引可以按照主键来排序存储数据,这样在查找行的时候非常有效。举个例子,如果是一本汉语字典,我们想要查找“数”这个字,直接在书中找汉语拼音的位置即可,也就是拼音“shu”。这样找到了索引的位置,在它后面就是我们想要找的数据行。聚集索引也可以这样理解数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引。数据行的物理顺序与列值的顺序相同,如果我们查询id比较靠后的数据,那么这行数据的地址在磁盘中的物理地址也会比较靠后。而且由于物理排列方式与聚集索引的顺序相同,所以也就只能建立一个聚集索引了。非聚集索引:在数据库系统会有单独的存储空间存放非聚集索引,这些索引项是按照顺序存储的,但索引项指向的内容是随机存储的。也就是说系统会进行两次查找,第一次先找到索引,第二次找到索引对应的位置取出数据行。非聚集索引不会把索引指向的内容像聚集索引一样直接放到索引的后面,而是维护单独的索引表(只维护索引,不维护索引指向的数据),为数据检索提供方便。

第24课

为什么索引要存放到硬盘上?如何评价索引的数据结构设计的好坏?

数据库服务器有两种存储介质,分别为硬盘和内存。内存属于临时存储,容量有限,而且当发生意外时(比如断电或者发生故障重启)会造成数据丢失;硬盘相当于永久存储介质,这也是为什么我们需要把数据保存到硬盘上。

虽然内存的读取速度很快,但我们还是需要将索引存放到硬盘上,这样的话,当我们在硬盘上进行查询时,也就产生了硬盘的 I/O 操作。相比于内存的存取来说,硬盘的 I/O 存取消耗的时间要高很多。我们通过索引来查找某行数据的时候,需要计算产生的磁盘 I/O 次数,当磁盘 I/O 次数越多,所消耗的时间也就越大。如果我们能让索引的数据结构尽量减少硬盘的 I/O 操作,所消耗的时间也就越小

使用平衡二叉树作为索引的数据结构有哪些不足?

二分查找法是一种高效的数据检索方式,时间复杂度为 O(log2n)我们先来看下最基础的二叉搜索树(Binary Search Tree),搜索某个节点和插入节点的规则一样,我们假设搜索插入的数值为 key: 如果 key 大于根节点,则在右子树中进行查找;如果 key 小于根节点,则在左子树中进行查找;如果 key 等于根节点,也就是找到了这个节点,返回根节点即可。 但是存在特殊的情况,就是有时候二叉树的深度非常大。比如我们给出的数据顺序是 (5, 22, 23, 34, 77, 89, 91),而第二个树的深度是 7,最多需要 7 次比较才能找到节点。第二棵树也属于二分查找树,但是性能上已经退化成了一条链表,查找数据的时间复杂度变成了 O(n)。,人们提出了平衡二叉搜索树(AVL 树),它在二分搜索树的基础上增加了约束,每个节点的左子树和右子树的高度差不能超过 1,也就是说节点的左子树和右子树仍然为平衡二叉树。常见的平衡二叉树有很多种 ,包括平衡二叉搜索树、红黑树、数堆、伸展树。平衡二叉搜索树是最早提出来的自平衡二叉搜索树,当我们提到平衡二叉树时一般指的就是平衡二叉搜索树。时间复杂度就是 O(log2n),树的深度也是 O(log2n),问题:但是当 n 比较大时,深度也是比较高的,如果我们把二叉树改成 M 叉树(M>2)?你能看到此时树的高度降低了,当数据量 N 大的时候,以及树的分叉数 M 大的时候,M 叉树的高度会远小于二叉树的高度。

B 树和 B+ 树的结构是怎样的?为什么我们常用 B+ 树作为索引的数据结构?

二叉树的问题及B树的好处:如果用二叉树作为索引的实现结构,会让树变得很高,增加硬盘的 I/O 次数,影响数据查询的时间。因此一个节点就不能只有 2 个子节点,而应该允许有 M 个子节点 (M>2)。B 树的出现就是为了解决这个问题,B 树的英文是 Balance Tree,也就是平衡的多路搜索树,它的高度远小于平衡二叉树的高度。在文件系统和数据库系统中的索引结构经常采用 B 树来实现。

B树的介绍:B 树作为平衡的多路搜索树,它的每一个节点最多可以包括 M 个子节点,M 称为 B 树的阶。同时你能看到,每个磁盘块中包括了关键字和子节点的指针。如果一个磁盘块中包括了 x 个关键字,那么指针数就是 x+1。对于一个 100 阶的 B 树来说,如果有 3 层的话最多可以存储约 100 万的索引数据。对于大量的索引数据来说,采用 B 树的结构是非常适合的,因为树的高度要远小于二叉树的高度。

一个 M 阶的 B 树(M>2)有以下的特性:

根节点的儿子数的范围是 [2,M]。每个中间节点包含 k-1 个关键字和 k 个孩子,孩子的数量 = 关键字的数量 +1,k 的取值范围为 [ceil(M/2), M]。叶子节点包括 k-1 个关键字(叶子节点没有孩子),k 的取值范围为 [ceil(M/2), M]。假设中间节点节点的关键字为:Key[1], Key[2], …, Key[k-1],且关键字按照升序排序,即 Key[i]<Key[i+1]。此时 k-1 个关键字相当于划分了 k 个范围,也就是对应着 k 个指针,即为:P[1], P[2], …, P[k],其中 P[1] 指向关键字小于 Key[1] 的子树,P[i] 指向关键字属于 (Key[i-1], Key[i]) 的子树,P[k] 指向关键字大于 Key[k-1] 的子树。所有叶子节点位于同一层。

B树举个例子如下

假设我们想要查找的关键字是 9,那么步骤可以分为以下几步:

我们与根节点的关键字 (17,35)进行比较,9 小于 17 那么得到指针 P1;按照指针 P1 找到磁盘块 2,关键字为(8,12),因为 9 在 8 和 12 之间,所以我们得到指针 P2;按照指针 P2 找到磁盘块 6,关键字为(9,10),然后我们找到了关键字 9

什么是 B+ 树,B+ 树基于 B 树做出了改进

有 k 个孩子的节点就有 k 个关键字。也就是孩子数量 = 关键字数,而 B 树中,孩子数量 = 关键字数 +1。非叶子节点的关键字也会同时存在在子节点中,并且是在子节点中所有关键字的最大(或最小)。非叶子节点仅用于索引,不保存数据记录,跟记录有关的信息都放在叶子节点中。而 B 树中,非叶子节点既保存索引,也保存数据记录。所有关键字都在叶子节点出现,叶子节点构成一个有序链表,而且叶子节点本身按照关键字的大小从小到大顺序链接。

举个B+的例子:上图就是一棵 B+ 树,阶数为 3,根节点中的关键字 1、18、35 分别是子节点(1,8,14),(18,24,31)和(35,41,53)中的最小值。每一层父节点的关键字都会出现在下一层的子节点的关键字中,因此在叶子节点中包括了所有的关键字信息,并且每一个叶子节点都有一个指向下一个节点的指针,这样就形成了一个链表。

举个B+的查询例子

与根节点的关键字 (1,18,35) 进行比较,16 在 1 和 18 之间,得到指针 P1(指向磁盘块 2)找到磁盘块 2,关键字为(1,8,14),因为 16 大于 14,所以得到指针 P3(指向磁盘块 7)找到磁盘块 7,关键字为(14,16,17),然后我们找到了关键字 16,所以可以找到关键字 16 所对应的数据。

B+和B做比较,B+树的好处

首先,B+ 树查询效率更稳定。因为B+ 树每次只有访问到叶子节点才能找到对应的数据,而在 B 树中,非叶子节点也会存储数据,这样就会造成查询效率不稳定的情况,有时候访问到了非叶子节点就可以找到关键字,而有时需要访问到叶子节点才能找到关键字。

其次,B+ 树的查询效率更高,这是因为通常B+ 树比 B 树更矮胖(阶数更大,深度更低),查询所需要的磁盘 I/O 也会更少。同样的磁盘页大小,B+ 树可以存储更多的节点关键字。

不仅是对单个关键字的查询上,在查询范围上,B+ 树的效率也比 B 树高。这是因为所有关键字都出现在 B+ 树的叶子节点中,并通过有序链表进行了链接。而在 B 树中则需要通过中序遍历才能完成查询范围的查找,效率要低很多。

第25课

了解 MySQL 中的 Hash 索引,理解使用它的优点和不足。

采用 Hash 进行检索效率非常高,基本上一次检索就可以找到数据,而 B+ 树需要自顶向下依次查找,多次访问节点才能找到数据,中间需要多次 I/O 操作,从效率来说 Hash 比 B+ 树更快查询的流程:键值 key 通过 Hash 映射找到桶 bucket。在这里桶(bucket)指的是一个能存储一条或多条记录的存储单位。一个桶的结构包含了一个内存指针数组,桶中的每行数据都会指向下一行,形成链表结构,当遇到 Hash 冲突时,会在桶中进行键值的查找。Hash 冲突:如果桶的空间小于输入的空间,不同的输入可能会映射到同一个桶中,这时就会产生 Hash 冲突,如果 Hash 冲突的量很大,就会影响读取的性能。在 Hash 值相同的情况下,就会进一步比较桶(Bucket)中的键值,从而找到最终的数据行。

Hash 索引和 B+ 树索引的区别以及使用场景。

Hash索引和B+树的区别Hash 索引不能进行范围查询,而 B+ 树可以。这是因为 Hash 索引指向的数据是无序的,而 B+ 树的叶子节点是个有序的链表。Hash 索引不支持联合索引的最左侧原则(即联合索引的部分索引无法使用),而 B+ 树可以。对于联合索引来说,Hash 索引在计算 Hash 值的时候是将索引键合并后再一起计算 Hash 值,所以不会针对每个索引单独计算 Hash 值。因此如果用到联合索引的一个或者几个索引时,联合索引无法被利用。Hash 索引不支持 ORDER BY 排序,因为 Hash 索引指向的数据是无序的,因此无法起到排序优化的作用,而 B+ 树索引数据是有序的,可以起到对该字段 ORDER BY 排序优化的作用。同理,我们也无法用 Hash 索引进行模糊查询,而 B+ 树使用 LIKE 进行模糊查询的时候,LIKE 后面前模糊查询(比如 % 开头)的话就可以起到优化作用。使用场景等值查询来说,通常 Hash 索引的效率更高,不过也存在一种情况,就是索引列的重复值如果很多,效率就会降低。这是因为遇到 Hash 冲突时,需要遍历桶中的行指针来进行比较,找到查询的关键字,非常耗时。所以,Hash 索引通常不会用到重复值多的列上,比如列为性别、年龄的情况等。

补充

什么是联合索引:参考链接

第26课

什么情况下使用索引?当我们进行数据表查询的时候,都有哪些特征需要我们创建索引?

字段的数值有唯一性的限制,比如用户名频繁作为where查询条件的字段,尤其是在数据表大的情况下效果更明显需要经常GROUP BY和ORDER BY的列UPDATE DELETE的WHERE条件列 一般也需要创建索引DISTINCT字段需要创建索引多表连接join时候 连接的表数量尽量不要超过3张对WHERE条件创建索引对用于连接的字段创建索引,并且该字段在多张表中的类型必须一致

索引不是万能的,索引设计的不合理可能会阻碍数据库和业务处理的性能。那么什么情况下不需要创建索引?

WHERE条件(GROUP ORDER)中用不到的字段表记录太少字段中有大量重复的数据频繁更新的字段不一定要创建索引,因为维护索引需要很高的成本采用二叉树的数据结构,树的高度大,磁盘IO操作多,影响查询效率

创建了索引不一定代表一定用得上,甚至在有些情况下索引会失效。哪些情况下,索引会失效呢?又该如何避免这一情况?

第27课

数据库中的存储结构是怎样的?页、区、段和表空间分别指的是什么?

在数据库中,不论读一行,还是读多行,都是将这些行所在的页进行加载。也就是说,数据库管理存储空间的基本单位是页(Page)一个页中可以存储多个行记录(Row),同时在数据库中,还存在着区(Extent)、段(Segment)和表空间(Tablespace)。行、页、区、段、表空间的关系如下图所示:

区(Extent)是比页大一级的存储结构,在 InnoDB 存储引擎中,一个区会分配64 个连续的页。因为 InnoDB 中的页大小默认是 16KB,所以一个区的大小是 64*16KB=1MB。段(Segment)由一个或多个区组成,区在文件系统是一个连续分配的空间(在 InnoDB 中是连续的 64 个页),不过在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。表空间(Tablespace)是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间数据库由一个或多个表空间组成表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间等。在 InnoDB 中存在两种表空间的类型:共享表空间和独立表空间。如果是共享表空间就意味着多张表共用一个表空间。如果是独立表空间,就意味着每张表有一个独立的表空间,也就是数据和索引信息都会保存在自己的表空间中。独立的表空间可以在不同的数据库之间进行迁移

为什么页(Page)是数据库存储空间的基本单位?

页(Page)如果按类型划分的话,常见的有数据页(保存 B+ 树节点)、系统页、Undo 页和事务数据页等。数据页是我们最常使用的页。

数据库 I/O 操作的最小单位是页

数据页包括七个部分,分别是文件头(File Header)、页头(Page Header)、最大最小记录(Infimum+supremum)、用户记录(User Records)、空闲空间(Free Space)、页目录(Page Directory)和文件尾(File Tailer)。

-

实际上,我们可以把这 7 个数据页分成 3 个部分。

首先是文件通用部分,也就是文件头和文件尾。它们类似集装箱,将页的内容进行封装,通过文件头和文件尾校验的方式来确保页的传输是完整的

在文件头中有两个字段,分别是 FIL_PAGE_PREV 和 FIL_PAGE_NEXT,它们的作用相当于指针,分别指向上一个数据页和下一个数据页。连接起来的页相当于一个双向的链表(物理上的连续,而是逻辑上的连续),如下图所示:

我们之前讲到过 Hash 算法,这里文件尾的校验方式就是采用 Hash 算法进行校验。举个例子,当我们进行页传输的时候,如果突然断电了,造成了该页传输的不完整,这时通过文件尾的校验和(checksum 值)与文件头的校验和做比对,如果两个值不相等则证明页的传输有问题,需要重新进行传输,否则认为页的传输已经完成。

第二个部分是记录部分,页的主要作用是存储记录,所以“最小和最大记录”和“用户记录”部分占了页结构的主要空间。另外空闲空间是个灵活的部分,当有新的记录插入时,会从空闲空间中进行分配用于存储新记录,如下图所示:

第三部分是索引部分,这部分重点指的是页目录,它起到了记录的索引作用,因为在页中,记录是以单向链表的形式进行存储的。单向链表的特点就是插入、删除非常方便,但是检索效率不高,最差的情况下需要遍历链表上的所有节点才能完成检索,因此在页目录中提供了二分查找的方式,用来提高记录的检索效率。这个过程就好比是给记录创建了一个目录: 将所有的记录分成几个组,这些记录包括最小记录和最大记录,但不包括标记为“已删除”的记录。第 1 组,也就是最小记录所在的分组只有 1 个记录最后一组,就是最大记录所在的分组,会有 1-8 条记录;其余的组记录数量在 4-8 条之间。这样做的好处是,除了第 1 组(最小记录所在组)以外,其余组的记录数会尽量平分。在每个组中最后一条记录的头信息中会存储该组一共有多少条记录,作为 n_owned 字段。页目录用来存储每组最后一条记录的地址偏移量,这些地址偏移量会按照先后顺序存储起来,每组的地址偏移量也被称之为槽(slot),每个槽相当于指针指向了不同组的最后一个记录。如下图所示:

-页目录存储的是槽,槽相当于分组记录的索引。我们通过槽查找记录,实际上就是在做二分查找。这里我以上面的图示进行举例,5 个槽的编号分别为 0,1,2,3,4,我想查找主键为 9 的用户记录,我们初始化查找的槽的下限编号,设置为 low=0,然后设置查找的槽的上限编号 high=4,然后采用二分查找法进行查找。

举例一个通过槽查询的例子:

首先找到槽的中间位置 p=(low+high)/2=(0+4)/2=2,这时我们取编号为 2 的槽对应的分组记录中最大的记录,取出关键字为 8。因为 9 大于 8,所以应该会在槽编号为 (p,high] 的范围进行查找

接着重新计算中间位置 p’=(p+high)/2=(2+4)/2=3,我们查找编号为 3 的槽对应的分组记录中最大的记录,取出关键字为 12。因为 9 小于 12,所以应该在槽 3 中进行查找。

遍历槽 3 中的所有记录,找到关键字为 9 的记录,取出该条记录的信息即为我们想要查找的内容。

从数据页的角度来看,B+ 树是如何进行查询的?

首先需要知道一棵 B+ 树按照节点类型可以分成两部分:

叶子节点,B+ 树最底层的节点,节点的高度为 0,存储行非叶子节点,节点的高度大于 0,存储索引键和页面指针,并不存储行记录本身。

解释在一棵 B+ 树中,每个节点都是一个页,每次新建节点的时候,就会申请一个页空间。同一层上的节点之间,通过页的结构构成一个双向的链表(页文件头中的两个指针字段)。非叶子节点,包括了多个索引行,每个索引行里存储索引键和指向下一层页面的页面指针。最后是叶子节点,它存储了关键字和行记录,在节点内部(也就是页结构的内部)记录之间是一个单向的链表但是对记录进行查找,则可以通过页目录采用二分查找的方式来进行

1补充:B+ 树是如何进行记录检索的?

如果通过 B+ 树的索引查询行记录,首先是从 B+ 树的根开始,逐层检索,直到找到叶子节点,也就是找到对应的数据页为止,将数据页加载到内存中,页目录中的槽(slot)采用二分查找的方式先找到一个粗略的记录分组,然后再在分组中通过链表遍历的方式查找记录。

2补充:普通索引和唯一索引在查询效率上有什么不同?

我们创建索引的时候可以是普通索引,也可以是唯一索引,那么这两个索引在查询效率上有什么不同呢?

唯一索引就是在普通索引上增加了约束性,也就是关键字唯一,找到了关键字就停止检索。而普通索引,可能会存在用户记录中的关键字相同的情况,根据页结构的原理,当我们读取一条记录的时候,不是单独将这条记录从磁盘中读出去,而是将这个记录所在的页加载到内存中进行读取。InnoDB 存储引擎的页大小为 16KB,在一个页中可能存储着上千个记录,因此在普通索引的字段上进行查找也就是在内存中多几次“判断下一条记录”的操作,对于 CPU 来说,这些操作所消耗的时间是可以忽略不计的。所以对一个索引字段进行检索,采用普通索引还是唯一索引在检索效率上基本上没有差别

第28课

数据库的缓冲池在数据库中起到了怎样的作用?如果我们对缓冲池内的数据进行更新,数据会直接更新到磁盘上吗?

磁盘 I/O 需要消耗的时间很多,而在内存中进行操作,效率则会高很多,为了能让数据表或者索引中的数据随时被我们所用,DBMS 会申请占用内存来作为数据缓冲池,这样做的好处是可以让磁盘活动最小化,从而减少与磁盘直接进行 I/O 的时间。要知道,这种策略对提升 SQL 语句的查询性能来说至关重要。如果索引的数据在缓冲池里,那么访问的成本就会降低很多。缓冲池如何读取数据呢?

缓冲池管理器会尽量将经常使用的数据保存起来,在数据库进行页面读操作的时候,首先会判断该页面是否在缓冲池中,如果存在就直接读取,如果不存在,就会通过内存或磁盘将页面存放到缓冲池中再进行读取

数据刷新到磁盘规则:我们对数据库中的记录进行修改的时候,首先会修改缓冲池中页里面的记录信息,然后数据库会以一定的频率刷新到磁盘上。注意并不是每次发生更新操作,都会立刻进行磁盘回写。缓冲池会采用一种叫做 checkpoint 的机制将数据回写到磁盘上,这样做的好处就是提升了数据库的整体性能。

缓冲池不够用:比如,当缓冲池不够用时,需要释放掉一些不常用的页,就可以采用强行采用 checkpoint 的方式,将不常用的脏页回写到磁盘上,然后再从缓冲池中将这些页释放掉。这里脏页(dirty page)指的是缓冲池中被修改过的页,与磁盘上的数据页不一致。

对数据页进行加载都有哪些方式呢?(一些操作执行可以再补充)

查看缓冲池的大小:show variables like 'innodb_buffer_pool_size'如果缓冲池中没有该页数据,那么缓冲池有以下三种读取数据的方式,每种方式的读取效率都是不同的:1内存读取:如果该数据存在于内存中,基本上执行时间在1ms左右,效率还是很高的2随机读取:如果数据没有在内存中,就需要在磁盘上对该页进行查找,整体时间预估在 10ms 左右,这10ms中有 6ms 是磁盘的实际繁忙时间(包括了寻道和半圈旋转时间),有 3ms 是对可能发生的排队时间的估计值,另外还有 1ms 的传输时间,将页从磁盘服务器缓冲区传输到数据库缓冲区中。这 10ms 看起来很快,但实际上对于数据库来说消耗的时间已经非常长了,因为这还只是一个页的读取时间。3顺序读取顺序读取其实是一种批量读取的方式,因为我们请求的数据在磁盘上往往都是相邻存储的,顺序读取可以帮我们批量读取页面,这样的话,一次性加载到缓冲池中就不需要再对其他页面单独进行磁盘 I/O 操作了。如果一个磁盘的吞吐量是 40MB/S,那么对于一个 16KB 大小的页来说,一次可以顺序读取 2560(40MB/16KB)个页,相当于一个页的读取时间为 0.4ms。采用批量读取的方式,即使是从磁盘上进行读取,效率也比从内存中只单独读取一个页的效率要高。

第29课(不是很懂)(待补充)

什么是索引片?如何计算过滤因子?

索引片就是 SQL 查询语句在执行中需要扫描的一个索引片段,我们会根据索引片中包含的匹配列的数量不同,将索引分成窄索引(比如包含索引列数为 1 或 2)和宽索引(包含的索引列数大于 2)。如果索引片越宽,那么需要顺序扫描的索引页就越多;如果索引片越窄,就会减少索引访问的开销。在索引片的设计中,我们还需要考虑一个因素,那就是过滤因子,它描述了谓词的选择性。在 WHERE 条件语句中,每个条件都称为一个谓词,谓词的选择性也等于满足这个条件列的记录数除以总记录数的比例

设计索引的时候,可以遵循哪些原则呢?

存在着一个三星索引的标准,这就好比我们在学习数据表设计时提到的三范式一样。三星索引具体指的是: 在 WHERE 条件语句中,找到所有等值谓词中的条件列,将它们作为索引片中的开始列;将 GROUP BY 和 ORDER BY 中的列加入到索引中;将 SELECT 字段中剩余的列加入到索引片中

为什么理想的索引很难在实际工作中应用起来?

但就同三范式一样,很多时候我们并没有遵循三范式的设计原则,而是采用了反范式设计。同样,有时候我们并不能需要完全遵循三星索引的原则,原因主要有以下两点:

采用三星索引会让索引片变宽,这样每个页能够存储的索引数据就会变少,从而增加了页加载的数量。从另一个角度来看,如果数据量很大,比如有 1000 万行数据,过多索引所需要的磁盘空间可能会成为一个问题,对缓冲池所需空间的压力也会增加。增加了索引维护的成本。如果我们为所有的查询语句都设计理想的三星索引,就会让数据表中的索引个数过多,这样索引维护的成本也会增加。举个例子,当我们添加一条记录的时候,就需要在每一个索引上都添加相应的行(存储对应的主键值),假设添加一行记录的时间成本是 10ms(磁盘随机读取一个页的时间),那么如果我们创建了 10 个索引,添加一条记录的时间就可能变成 0.1s,如果是添加 10 条记录呢?就会花费近 1s 的时间。从索引维护的成本来看消耗还是很高的。当然对于数据库来说,数据的更新不一定马上回写到磁盘上,但即使不及时将脏页进行回写,也会造成缓冲池中的空间占用过多,脏页过多的情况。

小总结

索引和锁是数据库中的两个核心知识点,不论在工作中,还是在面试中,我们都会经常跟它们打交道。之前我们已经从不同维度对索引进行了了解,比如 B+ 树、Hash 索引、页结构、缓冲池和索引原则等,了解它们的工作原理可以加深我们对索引的理解。同时在基础篇的部分中,我也讲解了事务的 4 大原则以及不同的隔离级别。这些隔离级别的实现都是通过锁来完成的,你可以思考下为什么我们需要给数据加锁呢?

第30课

就分类而言,锁的划分有多种方式,这些划分方式都包括哪些?(操作指令后面再补充)

我们可以从锁定对象的粒度大小,对锁进行划分,分别为行锁、页锁和表锁。

行锁就是按照行的粒度对数据进行锁定。锁定力度小,发生锁冲突概率低,可以实现的并发度高,但是对于锁的开销比较大,加锁会比较慢,容易出现死锁情况。页锁就是在页的粒度上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。当我们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。表锁就是对数据表进行锁定,锁定粒度很大,同时发生锁冲突的概率也会较高,数据访问的并发度低。不过好处在于对锁的使用开销小,加锁会很快。

不同的数据库和存储引擎支持的锁粒度不同

从数据库管理的角度对锁进行划分,共享锁和排它锁,是我们经常会接触到的两把锁。

共享锁也叫读锁或 S 锁,共享锁锁定的资源可以被其他用户读取,但不能修改。在进行SELECT的时候,会将对象进行共享锁锁定,当数据读取完毕之后,就会释放共享锁,这样就可以保证数据在读取时不被修改。排它锁也叫独占锁、写锁或 X 锁。排它锁锁定的数据只允许进行锁定操作的事务使用,其他事务无法对已锁定的数据进行查询或修改。

程序员的视角来看锁的话,可以将锁分成乐观锁和悲观锁,从名字中也可以看出这两种锁是两种看待数据并发的思维方式。

乐观锁(Optimistic Locking)认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,也就是不采用数据库自身的锁机制,而是通过程序来实现。在程序上,我们可以采用版本号机制或者时间戳机制实现。版本号机制:这种方式类似我们熟悉的 SVN、CVS 版本管理系统,当我们修改了代码进行提交时,首先会检查当前版本号与服务器上的版本号是否一致,如果一致就可以直接提交,如果不一致就需要更新服务器上的最新代码,然后再进行提交。时间戳机制:时间戳和版本号机制一样,也是在更新提交的时候,将当前数据的时间戳和更新之前取得的时间戳进行比较,如果两者一致则更新成功,否则就是版本冲突。悲观锁(Pessimistic Locking)也是一种思想,对数据被其他事务的修改持保守态度,会通过数据库自身的锁机制来实现,从而保证数据操作的排它性。

乐观锁和悲观锁的应用场景

乐观锁适合读操作多的场景,相对来说写的操作比较少。它的优点在于程序实现,不存在死锁问题,不过适用场景也会相对乐观,因为它阻止不了除了程序以外的数据库操作。

悲观锁适合写操作多的场景,因为写的操作具有排它性。采用悲观锁的方式,可以在数据库层面阻止其他事务对该数据的操作权限,防止读 - 写和写 - 写的冲突。

乐观锁和悲观锁并不是锁,而是锁的设计思想。

乐观锁和悲观锁的思想是什么?乐观锁有两种实现方式,这两种实现方式是什么?

上面提到了

多个事务并发,发生死锁时该如何解决?怎样降低死锁发生的概率?

发生死锁举例:在客户端 1 获取某数据行共享锁的同时,另一个客户端 2 也获取了该数据行的共享锁,这时任何一个客户端都没法对这个数据进行更新,因为共享锁会阻止其他事务对数据的更新,当某个客户端想要对锁定的数据进行更新的时候,就出现了死锁的情况。解决死锁:就需要一个事务进行回滚,另一个事务获取锁完成事务,然后将锁释放掉,很像交通堵塞时候的解决方案。预防死锁如果事务涉及多个表,操作比较复杂,那么可以尽量一次锁定所有的资源,而不是逐步来获取,这样可以减少死锁发生的概率;如果事务需要更新数据表中的大部分数据,数据表又比较大,这时可以采用锁升级的方式,比如将行级锁升级为表级锁,从而减少死锁产生的概率;不同事务并发读写多张数据表,可以约定访问表的顺序,采用相同的顺序降低死锁发生的概率。

第31课

MVCC 机制的思想是什么?为什么 RDBMS 会采用 MVCC 机制?

总起:有没有一种方式,可以不采用锁机制,而是通过乐观锁的方式来解决不可重复读和幻读问题呢?实际上 MVCC 机制的设计,就是用来解决这个问题的,它可以在大多数情况下替代行级锁,降低系统的开销。MVCC 是通过数据行的多个版本管理来实现数据库的并发控制,简单来说它的思想就是保存数据的历史版本。这样我们就可以通过比较版本号决定数据是否显示出来(具体的规则后面会介绍到),读取数据的时候不需要加锁也可以保证事务的隔离效果。通过 MVCC 我们可以解决以下几个问题: 读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。降低了死锁的概率。这是因为 MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。解决一致性读的问题。一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。

在 InnoDB 中,MVCC 机制是如何实现的 ?

Read View 是如何工作的?

第32课

什么是查询优化器?一条 SQL 语句的执行流程都会经历哪些环节,在查询优化器中都包括了哪些部分?

查询优化器的目标是找到执行 SQL 查询的最佳执行计划,执行计划就是查询树,它由一系列物理操作符组成,这些操作符按照一定的运算关系组成查询的执行计划。在查询优化器中,可以分为逻辑查询优化阶段和物理查询优化阶段逻辑查询优化就是通过改变 SQL 语句的内容来使得 SQL 查询更高效,同时为物理查询优化提供更多的候选执行计划。通常采用的方式是对 SQL 语句进行等价变换,对查询进行重写,而查询重写的数学基础就是关系代数。对条件表达式进行等价谓词重写、条件简化,对视图进行重写,对子查询进行优化,对连接语义进行了外连接消除、嵌套连接消除等。逻辑查询优化是基于关系代数进行的查询重写,而关系代数的每一步都对应着物理计算,这些物理计算往往存在多种算法,因此需要计算各种物理路径的代价,从中选择代价最小的作为执行计划。在这个阶段里,对于单表和多表连接的操作,需要高效地使用索引,提升查询效率。 在这两个阶段中,查询重写属于代数级、语法级的优化,也就是属于逻辑范围内的优化,而基于代价的估算模型是从连接路径中选择代价最小的路径,属于物理层面的优化。

查询优化器的两种优化方式分别是什么?

第一种是基于规则的优化器(RBO,Rule-Based Optimizer),规则就是人们以往的经验,或者是采用已经被证明是有效的方式。通过在优化器里面嵌入规则,来判断 SQL 查询符合哪种规则,就按照相应的规则来制定执行计划,同时采用启发式规则去掉明显不好的存取路径。

第二种是基于代价的优化器(CBO,Cost-Based Optimizer),这里会根据代价评估模型,计算每条可能的执行计划的代价,也就是 COST,从中选择代价最小的作为执行计划。相比于 RBO 来说,CBO 对数据更敏感,因为它会利用数据表中的统计信息来做判断,针对不同的数据表,查询得到的执行计划可能是不同的,因此制定出来的执行计划也更符合数据表的实际情况。

基于代价的优化器是如何统计代价的?总的代价又如何计算?(不全)

待补充

如有错误,欢迎指出。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。