这是 HITsz 数据库笔记,欢迎到我的 GitHub 上查看,有笔记说明和源码,作业和实验报告,希望对你有帮助
博客园显示图片异常
通过抽象来对用户屏蔽复杂性,以简化用户与系统的交互。
物理层(或内部层):
最低层次的抽象,描述数据实际上是怎样存储的和复杂的底层数据结构(存储路径、存储方式、索引方式)。
逻辑层(或概念层):
比物理层层次稍高的抽象,描述数据库中存储什么数据及这些数据间关系。
视图层(或外部层):
最高层次的抽象,只描述整个数据库的某个部分。用于并不需要关心所有的信息,而只需要访问数据库的一部分的用户。同一数据库有多个视图。
实例:
特定时刻存储在数据库中的信息的集合称作数据库的一个实例。
模式:
数据库的总体设计称作数据库模式(schema),是对数据库中数据所进行的一种结构性的描述。
数据库模式对应于程序设计语言中的变量声明(以及与之关联的类型的定义)。
每个变量在特定的时刻会有特定的值,程序中变量在某一时刻的值对应于数据库模式的一个实例。
在不同抽象层次描述数据库,就可定义出物理模式,逻辑模式和视图模式。
相同模式有不同名称:
视图英文
三级模式两层映像结构中的名字
External Schema
外模式
局部模式️
视图模式
用户模式
子模式
(Conceptual) Schema
概念模式
全局模式
逻辑模式
也可简称「模式」
Internal Schema
内模式
物理模式
存储模式
两层映像:
E-C Mapping(External Schema-Conceptual Schema Mapping):
将外模式映射为概念模式,从而支持实现数据概念视图向外部视图的转换,便于用户观察和使用。
逻辑数据独立性️:
当概念模式变化时,可以不改变外部模式(只需改变 E-C Mapping),从而无需改变应用程序。
C-I Mapping(Conceptual Schema-Internal Schema Mapping):
将概念模式映射为内模式,从而支持实现数据概念视图向内部视图的转换,便于计算机进行存储和处理。
物理数据独立性:
当内部模式(物理模式)变化时,可以不改变概念模式(只需改变 C-I Mapping),从而不改变外部模式。
数据库结构的基础是数据模型。
PPT 中的分类(往年考过三个模型的优缺点️):
层次模型按树的形式组织数据:
优点:
缺点:
网状模型按图的形式组织数据:
优点:
能够更为直接地表示现实世界,具有良好的性能,存取效率高。
缺点:
层次结构和网状结构共有的缺点:
- 数据之间的关联关系由复杂的指针系统来维系,结构描述复杂
- 数据检索操作依赖于由指针系统指示的路径
- 不能有效支持记录集合的操作
关系模型按表的形式组织数据:
优点:
书上的分类:
关系模型:
用表的集合来表示数据和数据间的联系。
实体 - 联系(E-R,entity-relationship)模型:
关系模型是数据模型,而 E-R 模型是概念模型。
基于对象的数据模型(应该不怎么学)
数据库系统:
数据库语言(DBMS 提供的):
数据定义语言(DDL,Data Definition Language):
定义表名,表标题,列名及其结构形式。
新数据插入时(更新数据库时)要检查完整性约束,防止不符合规范的数据进入数据库:
域约束:
每个属性都必须对应于一个所有可能的取值构成的域(例如,整数型、字符型、日期/时间型)。域约束是完整性约束的最基本形式。
参照(引用)完整性:
一个关系中给定属性集上的取值也在另一关系的某一属性集的取值中出现。
断言:
一个断言就是数据库需要始终满足的某一条件(域约束和参照完整性约束是断言的特殊形式),例如:「每一学期每一个系必须至少开设 \(5\) 门课程」只能表达成一个断言。如果断言有效,则以后只有不破坏断言的数据库更新才被允许。
数据操纵语言(DML,Data Manipulation Language):
对数据库进行增、删、改、查等操作。
数据控制语言(DCL,Data Control Language):
对不同操作和用户的约束。
表的列,也叫字段/属性/数据项,包括列名和列值。
表的行,也叫元组(\(n\) 元组就是一个有 \(n\) 个值的元组)/记录。
域:
对于关系的每个属性,都存在一个允许取值的集合(即域),这组值具有相同的数据类型。
集合中元素的个数称为域的基数。
域的笛卡尔积:
一组域 \(D_1,D_2,\cdots,D_n\) 的笛卡尔积为:\(D_1×D_2×\cdots×D_n = \{ (d_1 , d_2 , … , d_n) | d_i∈D_i , i=1,\cdots,n\}\),笛卡尔积每个元素 \((d_1 , d_2 , … , d_n)\) 称作一个 \(n\) - 元组。
关系:
一组域 \(D_1,D_2,\cdots,D_n\) 的笛卡尔积的子集。
关系模式或表标题:
用 \(R(A_{1}: D_{1}, A_{2}: {D}_{2}, \ldots, {A}_{{n}}: {D}_{{n}})\) 表示,可简记为 \({R}({A}_{1}, {A}_{2}, \ldots, {A}_{{n}})\) 来描述关系:
列是同质:
每一列中的列值来自同一域,是同一类型的数据。
不同的列可来自同一个域
列位置互换性:
区分哪一列是靠列名,与列的顺序无关。
行位置互换性:
区分哪一行是靠某一或某几列的值(关键字/键字/码字)。
任意两个元组不能完全相同
属性不可再分特性(关系第一范式)
超码(superkey):
超码是一个或多个属性的集合,这些属性的组合可以使我们在一个关系中唯一地标识一个元组。
两个不同元组的超码属性的取值不会完全相同。
候选码:
如果一个超码的任意真子集都不能成为超码,这样的最小超码称为候选码。
主码:
当有多个候选码时,可以选定一个作为主码。
主属性与非主属性:
包含在任何一个候选码中的属性被称作主属性,其他属性被称作非主属性。
如果关系的所有属性组是这个关系的候选码,称为全码。
外码:
关系 \(R\) 中的一个属性组,它不是 \(R\) 的候选码,但它与另一个关系候选码(主码)相对应,则称这个属性组为 \(R\) 的外码。
实体完整性:
关系的主码中的属性值不能为空值。
参照完整性:
如果关系 \(R_1\) 的外码 \(a\) 与关系 \(R_2\) 的主码 \(a\) 相对应,则 \(R_1\) 中的每一个元组的 \(a\) 值或者等于 \(R_2\) 中某个元组的 \(a\) 值,或者为空值。
用户自定义完整性:
用户针对具体的应用环境定义的完整性约束条件。
这一章缺少了很关键的关系代数。
E-R 模型的基本观点:
世界是由一组称作实体的基本对象和这些对象之间的联系构成的。
实体:
实体是现实世界中可区别于所有其他对象的一个「事物」或「对象」。每个实体有一组性质(属性),其中一些性质的值可以唯一地标识一个实体。
实体集:
实体集是相同类型即具有相同性质(或属性)的一个实体集合。
注意:以上是书中的定义,PPT 中的「实体」= 书中的「实体集」,PPT 的「实例」= 书中的「实体」。
以下采用书本的定义描述。
联系集:
联系是指多个实体间的相互关联。
联系集是相同类型联系的集合。正规地说,联系集是 \(n \geqslant 2\) 个(可能相同的)实体集上的数学关系。如果 \(E_{1}, E_{2}, \cdots, E_{n}\) 为实体集,那么联系集 \(R\) 是
\[\{(e_{1}, e_{2}, \cdots, e_{n}) \mid e_{1} \in E_{1}, e_{2} \in E_{2}, \cdots, e_{n} \in E_{n}\}
\]
的一个子集(相当于实体集笛卡尔积的一个子集),而 \((e_{1}, e_{2}, \cdots, e_{n})\) 是一个联系,\(e_i\) 是 \(E_i\) 的属性集合的子集。
实体集之间的联系称为参与。实体集 \(E_{1}, E_{2}, \cdots, E_{n}\) 参与联系集 \(R\)。
举例:实体集 \(Student,Course\) 参与联系集 \(SC\)。
参与联系集的实体集的数目称为联系集的度。
实体在联系中扮演的功能称为实体的角色。
属性:
每个属性都有一个可取值的集合,称为该属性的域或者值集。
属性的分类:
简单(单一)属性:
它们不能划分为更小的部分。
复合属性:
将属性再划分为更小的部分(即其他属性)。
地址细分(省市)
单值属性:
对一个特定实体,一个属性都只有单独的一个值。
多值属性:
对某个特定实体而言,一个属性可能对应于一组值(即一个属性可以取多个值)。
一个人有多个电话号码
联系集的类型:
一对一:
\(A\) 中的一个实体至多与 \(B\) 中的一个实体相关联,并且 \(B\) 中的一个实体也至多与 \(A\) 中的一个实体相关联。
一对多:
\(A\) 中的一个实体可以与 \(B\) 中的任意数目(零个或多个)实体相关联,而 \(B\) 中的一个实体至多与 \(A\) 中的一个实体相关联。
多对一:
\(A\) 中的一个实体至多与 \(B\) 中的一个实体相关联,而 \(B\) 中的一个实体可以与 \(A\) 中任意数目(零个或多个)实体相关联。
多对多:
\(A\) 中的一个实体可以与 \(B\) 中任意数目(零个或多个)实体相关联,而且 \(B\) 中的一个实体也可以与 \(A\) 中任意数目(零个或多个)实体相关联。
映射基数(联系的基数️):
实体之间的联系的数量,即一个实体通过一个联系能与另一实体集相关联的实体的数目。
实体集和二元联系集之间的一条边,可以有一个关联的最大和最小的映射基数。
例如:
一个 instructor 可以对应 \(0\) 个或多个 student,但一个 student 有且仅有一个 instructor,注意 advisor 是从 instructor 到 student的一对多联系。
笔者注:
PPT 上的基数很多地方是反了,应该是自己的基数写在自己旁边(不知道祖传错误是否已被更正)
参与约束:
完全参与联系:
如果实体集 \(E\) 中的每个实体都参与到联系集 \(R\) 的至少一个联系中,此时最小基数为 \(1\)。
部分参与联系:
如果 \(E\) 中只有部分实体参与到 \(R\) 的联系中,此时最小基数为 \(0\)。
弱实体集和强实体集:
需求分析:
形成数据库设计的「源」清单和「属性」清单。
概念数据库设计:
用统一的表达方法,如 E-R 模型给出描述:
各种实体的发现、划分和定义
各种实体属性的发现、分析和定义
各种实体联系的发现、分析和定义
外部视图(模式)和概念视图(模式)的定义
消除冲突:
属性冲突:
结构冲突:
命名冲突(同名异义,异名同义)
逻辑数据库设计:
E-R 图转换为关系模式:
复合属性转化:
多值属性转化:
一对一联系:
一对多联系:
将单方(如教师和学生关系中的教师)参与实体的关键字,作为多方(学生)参与实体对应关系的属性。
多对多联系:
将联系定义为新的关系,属性为参与双方实体的关键字。
冗余(没懂):
受控冗余:
有时为了效率和便利,会特意设计冗余数据。
非受控冗余:
存在传递函数依赖。
函数依赖定义:
设 $ {R}$ 是属性集合 \(U=\{A_{1}, A_{2}, \ldots, A_{n}\}\) 上的一个关系模式,\(X, Y\) 是属性集 \(U\) 上的两个子集。
若对 \(R\) 的任意关系 \(r\),$ {r}$ 中不可能有两个元组满足在 \(X\) 中的属性值相等而在 \(Y\) 中的属性值不等,则称「\(X\) 函数决定 \(Y\)」或「\(Y\) 函数依赖于 \(X\)」,记作 \(X \rightarrow Y\),称 \(X\) 为决定因素,\(Y\) 为依赖因素。
关系模式是在属性集合上的结构化描述,关系是表状结构的。
函数依赖,顾名思义,\(X\) 映射到 \(Y\),同一个 \(x\) 不会映射到两个 \(y\)。
函数依赖的特性:
如果 \(Y\sub X\),则有 \(X\to Y\),这是平凡的函数依赖。
对 $ {X} \rightarrow {Y}$,但 $ {Y} \not \subset {X}$,则称 \(X\rightarrow {Y}\) 为非平凡的函数依赖。
若 \(X \rightarrow Y, Y \rightarrow X\),则记作 \(X \leftrightarrow Y\)。
若 \(Y\) 不函数依赖于 \(X\). 则记作 \(X \not\rightarrow Y\)。
$ {X} \rightarrow {Y}$,若是基于模式 \(R\) 的,则对任意的关系 \(r\) 成立;若仅基于具体关系 $ {r}$ 的,则可能只对该关系 \(r\) 成立。
如一关系 \(r\) 的某属性集 \(X\),\(r\) 中没有 \(X\) 上相等的两个元组,则 $ {X} \rightarrow {Y}$ 恒成立。
完全函数依赖和部分函数依赖:
在 \(R\) 中,若 \(X \rightarrow Y\) 并且对于 \(X\) 的任何真子集 \(X'\) 都有 \(X'\not\rightarrow Y\),则称 \(Y\) 完全(full)函数依赖于 \(X\),记为\(X\stackrel{{f}}{\rightarrow} {Y}\),否则称 \(Y\) 部分(partial)函数依赖于 \({X}\),记为 \({X} \stackrel{{p}}\to {Y}\)。
$ {R}$ 是属性集合 \(U\) 上的一个关系模式,若 \(X\sub U\) 且 \(X\stackrel{{f}}{\rightarrow} {U}\),则称 \(X\) 为 \(R\) 的候选键(候选码)。
传递函数依赖:
在 \(R\) 中,若 \(X\to Y,Y \to Z\),其中 \(Y\not\sub X,Z\not\sub Y,Z\not\sub X,Y\not\to X\)(说明都是非平凡依赖),则称 \(Z\) 传递函数依赖于 \(X\)。
传递依赖的判定很严格,仅满足 \(X\to Y,Y \to Z\) 是不够的,要特别注意。
逻辑蕴涵:
设 \(F\) 是关系模式 \(R\) 的一个函数依赖集合,\(X,Y\) 是 \(R\) 的属性子集,若从 \(F\) 的函数依赖能够推导出 \(X \rightarrow Y\),则称 \(F\) 逻辑蕴涵 \(X \rightarrow Y\),记作 \({F} \models {X} \rightarrow{Y}\)。
闭包:
令 \(F\) 为一个函数依赖集,\(F\) 的闭包是被 \(F\) 逻辑蕴涵的所有函数依赖的集合, 记作 \(F^{+}\)。若 \(F^+=F\),则 \(F\) 是一个全函数依赖族(函数依赖完备集)。
在下面的规则中去寻找逻辑蕴涵的函数依赖,通过反复应用这些规则,可以找到给定 \(F\) 的全部 \(F^{+}\)。
用希腊字母 \((\alpha, \beta, \gamma, \cdots)\) 表示属性集,用 \(\alpha \beta\) 表示 \(\alpha \cup \beta_{\circ}\)
这组规则称为 Armstrong 公理(据说不怎么会考):
自反律:
若 \(\alpha\) 为一属性集且 \(\beta \subseteq \alpha\),则 \(\alpha \rightarrow \beta\) 成立。
增补律:
若 \(\alpha \rightarrow \beta\) 成立且 \(\gamma\) 为一属性集,则 \(\gamma \alpha \rightarrow \gamma \beta\) 和 \(\gamma \alpha \rightarrow \beta\) 成立。
传递律:
\(\alpha \rightarrow \beta\) 和 \(\beta \rightarrow \gamma\) 成立,则 \(\alpha \rightarrow \gamma\) 成立。
公理可导出的比较好用的引理:
合并律:
若 \(\alpha \rightarrow \beta\) 和 \(\alpha \rightarrow \gamma\) 成立, 则 \(\alpha \rightarrow \beta \gamma\) 成立。
分解律:
若 \(\alpha \rightarrow \beta \gamma\) 成立, 则 \(\alpha \rightarrow \beta\) 和 \(\alpha \rightarrow \gamma\) 成立。
伪传递律:
若 \(\alpha \rightarrow \beta\) 和 \(\gamma \beta \rightarrow \delta\) 成立, 则 \(\alpha \gamma \rightarrow \delta\) 成立(由 \(\alpha \rightarrow \beta\) 得到 \(\gamma\alpha \rightarrow \gamma\beta\),再由传递律得到结论)。
属性集闭包️:
对 $ {R}( {U}, {F}), {X} \subseteq {U}, {U}={ {A}{1}, {~A}{2}, \ldots, {A}{ {n}}}$,令 \({X}^{+}_{F}=\{ {A}_{ {i}} \mid\)用 \(\text{Armstrong}\) 公理可从 \(F\) 导出 \({X} \rightarrow {A}_{ {i}}\}\) 称 $ {X}^{+}{ }{ {F}}$ 为 \(X\) 于 \(F\) 的属性集闭包。
显然 \(X \subseteq X^{+}{ }_{F}\)。
引理:
$ {X} \rightarrow {Y}$, 当且仅当 $ {Y} \subseteq {X}_{ {F}}^{+}$。
求 \(X\) 的属性集闭包的算法️:
覆盖:
对 \(R(U)\) 上的两个函数依赖集合 \(F,G\),如果 \(F^+= G^+\),则称 \(F\) 和 \(G\) 是等价的,也称 \(F\) 覆盖 \(G\) 或者 \(G\) 覆盖 \(F\)。
最小覆盖:
若 \(F\) 满足以下条件,则称 \(F\) 为最小覆盖(最小依赖集,最小等价依赖集):
求最小覆盖的算法:
第一范式:
如果关系模式 \(R\) 所有属性的域都是原子,则称 \(R\) 属于第一范式(\(1NF\)), 记作 \(R \in 1NF\)。
$1NF $ 要求关系中不能有复合属性、多值属性及其组合。
多值属性解决方案:
- 拆成两个表,原表去掉多值列,然后把多值属性和主码作为一个新的表。
- 拆成多行
- 拆成多列
第二范式:
若 \(R(U)\in 1NF\) 且 \(U\) 中的每一非主属性完全函数依赖于候选键,则称 \(R(U)\) 属于第二范式,记为:\(R(U)\in 2NF\)。
第二范式消除非主属性对候选键的部分依赖。
将候选键拆成多个表,每个表的非主属性完全函数依赖于候选键。
第二范式只有历史意义,已经不在实际中使用了。
第三范式:
若 \(R(U,F)\in 2NF\), 在 \(R\) 中若不存在候选键 \(X\),属性集 \(Y\)(不可以是候选键),和非主属性 \(Z\),使得 \(X\to Y,Y \to Z\) 成立,其中 \(Y\not\sub X,Z\not\sub Y,Z\not\sub X,Y\not\to X\),则称 \(R\) 属于第三范式,记为:\(R\in 3NF\)。
第 \(3\) 范式消除了非主属性对侯选键的传递依赖。
如满足第 \(3\) 范式,则一定能满足第 \(2\) 范式。
Boyce-Codd 范式:
若 \(R(U,F)\in 1NF\), 若对于任何 \(X\to Y\in F\)(或 \(X\to A\in F\)),当 \(Y\not\sub X\)(或 \(A\in X\))时,\(X\) 必含有候选键(即超码),则称 \(R(U)\) 属于 Boyce-Codd 范式,记为:\(R(U)\in BCNF\)。
如满足 Boyce-Codd 范式,则一定能满足第 \(3\) 范式。
多值依赖:
对 \(R(U)\),设 \(X,Y\sub U\),若对于 \(R(U)\) 的任一关系 \(r\),若元组 \(t\in r, s\in r,{t}[ {X}]= {s}[ {X}]\),则必有 $ {u} \in {r}, {v} \in {r}$ 使得:
\(u[X]=v[X]=t[X]=s[X]\)
$ {u}[ {Y}]= {t}[ {Y}]$ 且 \(u[ {U}- {X}- {Y}]= {s}[ {U}- {X}- {Y}]\)
$ {v}[ {Y}]= {s}[ {Y}]$ 且 \(v[U- {X}- {Y}]= {t}[ {U}- {X}- {Y}]\)
\(X\)
\(Y\)
\(U-X-Y\)
\(x\)
\(y_1\)
\(z_1\)
\(x\)
\(y_2\)
\(z_2\)
\(x\)
\(y_2\)
\(z_1\)
\(x\)
\(y_1\)
\(z_2\)
均成立,则称 \(Y\) 多值依赖于 \(X\),或说 \(X\) 多值决定 $ {Y}$,记作 $ {X} \rightarrow \rightarrow {Y}$,同时 \(X \to\to U-X-Y\)。
若 \(U=X\cup Y\),则一定有 \(X\to\to Y\)。
直观地,若 $ {X} \rightarrow \rightarrow {Y}$,对于 \(X\) 给定值,\(Y\) 有一组值与之对应(\(0\) 或 \(n\) 个)且这组 \(Y\) 值不以任何方式与 \(U-X-Y\) 中属性值相联系。
第四范式(基于多值依赖的 BCNF 范式,不考):
函数依赖和多值依赖集为 \(F\) 的关系模式 \(R(U)\) 属于第四范式 \((4 {NF})\) 的条件是,对 \(F^{+}\)中所有形如 \(\alpha \rightarrow \beta\) 的多值依赖(其中 \(\alpha \subseteq U\) 且 \(\beta \subseteq U\)),至少有以下之一成立:
模式分解定义:
关系模式 \(R(U)\) 的分解是指用 \(R\) 的一组子集 \(\rho=\{ {R}_{1}, \ldots, {R}_{ {k}}\}\) 来代替它。其中 $ {U}= {U}{1} \cup {U}{2} \cup \ldots \cup {U}{ {k}},{U}{ {i}} \notin {U}{ {j}}( {i} \neq {j})$。对于关系模式 \(R\) 的任一关系 \(r\),它向 \(\rho\) 的投影连接记为 $ {m}{\rho}( {r})$ :
\[{m}_{\rho}( {r})=\pi_{ {R}_{1}}( {r}) \bowtie \ldots \bowtie \pi_{ {R}_{ {k}}}( {r})=\bowtie_{( {i}=1, \ldots, {k})} \pi_{ {R}_{ {i}}}( {r})
\]
这里 : \(\pi_{ {R}_{ {i}}}( {r})=\{ {t}[ {R}_{ {i}}] \mid {t} \in {r}, {i}=1, \ldots, {k}\}\)。
若对关系模式 \(R\) 的任一关系 \(r\) 都有 \(r = m_\rho(r)\) 成立,则称 \(\rho\) 是 \(R\) 相对于 \(F\) 的一个无损连接分解。
无损连接分解检验算法:
构造一 \(k\) 行 \(n\) 列的表(\(k\) 是分解的集合数,\(n\) 是 \(R(U)\) 属性数),可称为 $ {R}_\rho$ 表。
其中第 \(j\) 列对应于 $ {A}{ {j}}$,第 \(i\) 行对应于 $ {R}{ {i}}$。
若 $ {A}{ {j}} \in {R}{ {i}}$,则 $ {R}{\rho}$ 表中第 \(i\) 行第 \(j\) 列位置填写符号 $ {a}{ {j}}$,否则填写 $ {b}_{ {ij}}$。
根据 \(\forall( {X} \rightarrow {Y}) \in {F}\),对 $ {R}_{\rho}$ 表进行修改:
给定 $ {X} \rightarrow {Y}\(,寻找 **\)X$ 属性取值相同**的行,用其值重置 \(Y\) 属性值。
修改后,如果有一行变成 $ {a}{1}, {a}{2}, \ldots, {a}_{ {n}}$,则 \(\rho\) 是无损连接分解,否则为有损连接分解。
笔者认为这个算法应该循环,直到不能再修改为止,但是老师说原论文也没有提到做多几次的事情,只需要做一遍就行,遂躺平做一遍就好。
对于分解成两个子集的情况,可以快速判断的方法:
设 \(F\) 是关系模式 \(R\) 上的一个函数依赖集合,\(\rho=\{ {R}_{1}, {R}_{2}\}\) 是 \(R\) 的一个分解。
则当且仅当 $ {R}{1} \cap {R}{2} \rightarrow {R}{1}- {R}{2}$ 或者 $ {R}{1} \cap {R}{2} \rightarrow {R}{2}- {R}{1}$ 属于 \(F^+\) 时,\(\rho\) 是关于\(F\) 无损连接的。
因为 \(R_1=(R_1\cap R_2) \cup(R_1-R_2),R_2=(R_1\cap R_2) \cup(R_2-R_1)\),所以有
\(R_1\cap R_2\)
\(R_1- R_2\)
\(R_2- R_1\)
\(R_1\)
\(a_1\)
\(a_2\)
\(b_{13}\)
\(R_2\)
\(a_1\)
\(b_{22}\)
\(a_3\)
此时只要 $ {R}{1} \cap {R}{2} \rightarrow {R}{1}- {R}{2}$ 或者 $ {R}{1} \cap {R}{2} \rightarrow {R}{2}- {R}{1}$ 属于 \(F^+\) 的一个成立,就能实现有一行变成 $ {a}{1}, {a}{2},{a}_3$。
保持依赖分解的分解️:
保持依赖分解检测算法:
对 \(F\) 中的每一个 \(\alpha \rightarrow \beta\),
\(result \leftarrow \alpha\)
\(\bf while\)
\(\bf foreach\) 分解后包含有 \(\alpha\) 的 \(R_{i}:\)
\(t\leftarrow ( result \cap R_{i})^{+} \cap R_{i}\)
\(result\leftarrow result \cup t\)
\(\mathbf{until} ~(~result\) 没有变化 \()\)
如果 \(result\) 含 \(\beta\) 中的所有属性,则函数依赖 \(\alpha\to \beta\) 保持。
BCNF 分解 + 无损连接分解:
BCNF 范式要求每个函数依赖的左侧都要是关系模式的超码(含有候选键)。
仅 BCNF 分解:
将左侧不含候选键的函数依赖单独组成一个关系,将包含候选键的组成一关系。
无损连接分解成 BCNF:
计算 \(F^{+}\)
\(\mathbf{while}
result \not\in BCNF\mathbf{do}:\)\(\mathbf{if~} result\) 中存在模式 \(R_{i}\) 不属于 \(BCNF ~\mathbf{then}\):
对于 \(R_{i}\) 上所有非平凡函数依赖 \(\alpha \rightarrow \beta\) ( 满足 \(\alpha \rightarrow R_{i}\) 不属于 \(F^{+}\),即 \(\alpha\) 不是 \(R_i\) 的超码)
\(\quad\) $result =( result -R_{i}) \cup(R_{i}-\beta) \cup(\alpha, \beta) $
// 即用两个模式 \((R_{i}-\beta) \cup(\alpha, \beta)\) 取代原来的 \(R_i\),由之前的快速判断的方法可知分解无损
\(3NF\) 分解 + 保持依赖分解(+ 无损连接分解):
仅 \(3NF\) 分解:
将每一个函数依赖单独组成一个关系。
保持依赖分解成 \(3NF\):
关系模式 \(R(U, F)\),\(F\) 是函数依赖集最小覆盖,求保持依赖的 \(3NF\) 分解 \(\rho\)。
若有 $ {X} \rightarrow {A}{1}, {X} \rightarrow {A}{2}, \ldots, {X} \rightarrow {A}{ {m}}\in{F}$,则以 $ {XA}{1} {~A}{2} \ldots {A}{ {m}}$ 组成一模式 \(R_i\),\(\rho=\rho\cup R_i\)
这样保证 \(R_i\) 是 \(3NF\) 分解:
因为 \(F\) 是最小覆盖,要求对任何 \(\{X\to A\}\in F\),有 \(F- \{ X\to A \}\) 不等价于 \(F\)。
所以一定不会存在 \(A_i\to A_j\in F(i\not=j)\) 的情况,否则 \(F-\{X\to A_j\}\) 仍等价于 \(F\)。
上一步处理完后,把 \(R\) 中不出现在 \(F\) 中的属性去掉并单独组成一模式 \(R_{no}\),令 \(\rho=\rho\cup R_{no}\)。
若要达到无损连接分解:
如果 \(R_{no}\) 存在,将在 \(R_{no}\) 加入候选键属性,则能使该分解达到无损。
如果不存在,则判断所得 \(\rho\) 集合中的是否存在一个 \(R_i\) 包含了候选键属性集:
存在,算法结束。
不存在,\(\rho=\rho~\cup\) 候选键属性集。
存储体系:
操作系统对数据的组织:
FAT(文件分配表 - File Allocation Table)- 目录(文件夹)- 磁盘块/簇
对于一个文件,用 FAT 找到它在磁盘中的位置。
内存管理:
一条记录的地址 = 存储单元的地址 = 内存地址 = 页面 :页内偏移量
磁盘结构:
磁盘数据读写时间️:
包括寻道时间(约在 \(1-20ms\)),旋转时间(约 \(0-10ms\))和传输时间。
磁盘以 \(7200\) 转/\(min\) 旋转,即 \(8.33ms\) 内旋转一周
柱面之间移动磁头组合从启动到停止花费 \(1 ms\)。
每移动 \(4000\) 个柱面另加 \(1ms\),即磁头在 \(0.00025 ms\) 内移动一个磁道,从最内圈移动到最外圈,移动 \(65536\) 个磁道大约用 \(0.00025\times 65536 + 1=17.38ms\)。
一个磁道中扇区间的空隙大约占 \(10 \%\) 的空间
一个磁盘块 \(=4\) 个扇区 \(=16384\) 个字节
最短时间 \(=\) 传输时间大约是 \(0.13ms\)
最长时间 \(=\) 寻道时间+旋转时间+传输时间 \(=17.38+8.33+0.13=25.84 m s\)
平均时间 \(=6.46(16.38/3+1)+4.17(8.33/2)+0.13=10.76 ms\) (括号表示这个数是怎么得到的)
概率论忘掉了,这里可能有算错的地方(这是不会考的,不重要)
平均寻道时间要除以 \(3\) 是求了一个盘片内径向任意两点之间移动时间的期望(前提假定为平均分布)。
设盘片半径为 \(r\)。令 \(f(y)=|x-y|\),即表示径向从 \(y\) 点到 \(x\) 点之间距离。
当 \(x\) 为 \([0,r]\) 上的固定值时,求出 \([0,r]\) 上所有点到 \(x\) 的期望距离:
\[\mathbb{E}f(y)=\int^r_0|x-y|\frac{1}{r}dy=\frac{x^2}{r}-x+\frac{r}{2}
\]对于所有 \(x\in[0,r]\),计算期望距离的期望:
\[\mathbb{E}\mathbb{E}f(y)=\int^r_0\frac{1}{r}(\frac{x^2}{r}-x+\frac{r}{2})dx=\frac{r}{3}
\]即磁头平均会移动 \(\begin{aligned}\frac{r}{3}\end{aligned}\)。
物理存取算法考虑的关键:
独立磁盘冗余阵列(Redundant Array of Independent Disk,RAID 技术)(理解):
块级拆分:
一个文件由多个块组成,不同块存储于不同磁盘。
比特级拆分:
一个字节被拆分成 \(8\) 个比特位, 不同比特位存储于不同磁盘。
RAID \(0\) 级:
块级拆分但没有任何冗余(例如镜像或奇偶校验位)的磁盘阵列。
RAID \(1\) 级:
使用块级拆分的磁盘镜像,每一个磁盘有一个镜像磁盘。
RAID \(2\) 级:
位交叉纠错处理,\(4\) 个磁盘存储 \(4\) 位 \(+~3\) 个校验盘存储 \(3\) 校验位(汉明码)。
RAID \(3\) 级:
RAID \(2\) 级的改进,只用一个校验盘,比 RAID \(2\) 级常用。
RAID \(4\) 级:
块交叉的奇偶校验组织结构。它像 RAID \(0\) 级一样使用块级拆分,此外在一张独立的磁盘上为其他 \(N\) 张磁盘上对应的块保留一个奇偶校验块。
RAID \(5\) 级:
块交叉的分布奇偶校验位的组织结构,是 RAID \(4\) 级的改进,将数据和奇偶校验位都分布到所有的 \(N + 1\) 张磁盘中。
数据存储的映射:
数据库记录在磁盘上的存储:
定长记录(根据偏移量区分记录):
所有字段固定位数(黑色是空着的)。
变长记录(靠分隔符区分开始与结束):
块头包括:
实际记录从块的尾部开始连续排列。
块中空闲空间是连续的,无论是插入操作还是删除操作都不能改变这一点。
如果插入一条记录,在空闲空间的尾部给这条记录分配空间,并且将包含这条记录大小和位置的条目添加到块头中。
如果一条记录被删除:
移动记录的代价并不高,因为块的大小是有限制的,典型的值为 \(4-8\)KB。
数据库 - 表所占磁盘块的分配方法:
连续分配:
数据块被分配到连续的磁盘块上(会存在扩展困难问题)。
链接分配:
数据块中包含指向下一数据块的指针(访问速度问题),不连续,有空位就放。
按簇分配:
按簇分配,簇内连续分配,簇之间靠指针连接,簇有时也称片段,结合前面两者优点。
索引分配:
索引块中存放指向实际数据块的指针(可以不用 FAT)。
文件组织方法️:
考试重点,注意区分,回答合理即可。
无序文件组织:
无序记录文件(堆文件 heap 或 pile file)
记录可存储于任意有空间的位置,磁盘上存储的记录是无序的。更新效率高,但检索效率可能低。
一开始新记录总插入到文件尾部。删除记录时,可以直接删除该记录所在位置的内容,也可以在该记录前标记「删除标记」,新增记录可以利用那些标记为「删除标记」的记录空间
频繁删增记录时会造成空间浪费,所以需要周期性重新组织数据库。
数据库重组是通过移走被删除的记录使有效记录连续存放,从而回收那些由删除记录而产生的未利用空间(外部碎片)。
有序文件组织
有序记录文件(排序文件 Sequential)
记录按某属性或属性组值的顺序插入,磁盘上存储的记录是有序的。检索效率可能高,但更新效率低。
当按排序字段进行检索时,速度得到很大提高。但当按非排序字段检索时,速度可能不会提高很多。
用于存储排序的属性通常称为排序字段,可以是关系中的主码,所以又称排序码。
改进办法(使用溢出):
为将来可能插入元组预留空间(这可能造成空间浪费),或使用一个临时的无序文件(被称为溢出文件)保留新增的记录。
当采取溢出文件措施时,检索操作既要操作主文件,又要操作溢出文件。
需要周期性重新组织数据库,将溢出文件合并到主文件,并恢复主文件中的记录顺序。
散列文件组织:
聚簇文件组织:
聚簇文件(Clustering file)
聚簇:
将具有相同或相似属性值的记录存放于连续的磁盘簇块中,优化连接代价,在不用索引的时候使用。
多表聚簇:
将若干个相互关联的表存储于一个文件中,可提高多表情况下的查询速度。
何时使用多表聚簇依赖于数据库设计者所认为的最频繁的查询类型。
索引定义:
定义在存储表基础上,无需检查所有记录,快速定位所需记录的一种辅助存储结构,由一系列存储在磁盘上的索引项组成,每一索引项又由两部分构成:
索引字段:
由表中某些列中的值串接而成,类似于词典中的词条。索引中通常存储了索引字段的每一个值。
行指针:
指向表中包含索引字段值的记录在磁盘上的存储位置,类似于词条在书籍、词典中出现的页码。
有索引时,更新操作必须同步更新索引文件和主文件。
对经常出现在检索条件、连接条件和分组计算条件中的属性可建立索引。
存储索引项的文件为索引文件,存储表称为主文件。
索引文件组织方式有两种️:
排序索引文件:
按索引字段值的某一种顺序组织存储。
散列索引文件:
依据索引字段值使用散列函数分配散列桶的方式存储。
主文件组织有堆文件、排序文件、散列文件、 聚簇文件等多种方式,和索引文件组织方式区分。
索引应用的评价:
访问时间:
在查询中使用该技术找到一个特定数据项或数据项集所需的时间
插入时间:
插入一个新数据项所需的时间。该值包括找到插入这个新数据项的正确位置所需的时间,以及更新索引结构所需的时间。
删除时间:
删除一个数据项所需的时间。该值包括找到待删除项所需的时间,以及更新索引结构所需的时间。
空间开销:
索引结构所占用的额外存储空间。倘若存储索引结构的额外空间大小适度,通常牺牲一定的空间代价来换取性能的提高是值得的。
码的区分(不太能区分):
排序码:
对主文件进行排序存储的那些属性或属性组。
索引码:
即索引字段,不一定具有唯一性。
搜索码:
在文件中查找记录的属性或属性集称为搜索码。
稠密索引:
主文件中每一个搜索码值(索引字段值)都有一个索引项和它对应。
对候选键建稠密索引:
主文件不用排序,直接可以定位。
非候选键建稠密索引:
索引文件中索引字段值是不重复的,主文件按索引字段排序。
索引文件中索引字段值是有重复的,主文件不排序。
引入指针桶处理非候选键索引的多记录情况,索引文件中索引字段值是不重复的,主文件不排序。
索引文件通常要排序。
稀疏索引:
稀疏索引只为主文件部分搜索码值(索引字段值)建立索引记录,主文件按索引字段排序。
主索引:
辅助索引:
主索引和辅助索引的区别:
- 一个主文件仅可以有一个主索引(只按主索引的搜索码排序),但可以有多个辅助索引。
- 主索引通常建立于主码/排序码上面,辅助索引建立于其他属性上面。
- 可以利用主索引重新组织主文件数据,辅助索引不能改变主文件数据。
- 主索引是稀疏索引,辅助索引是稠密索引
聚簇索引:
聚簇索引定义:
聚簇索引是指索引中邻近的记录在主文件中也是临近存储的。
聚簇字段定义:
如果主文件的某一排序字段不是主码,则该字段上每个记录取值便不唯一,此时该字段被称为聚簇字段。聚簇索引通常是定义在聚簇字段上。
非聚簇索引:
指索引中邻近的记录在主文件中不一定是邻近存储的。
聚簇索引和非聚簇索引的区别:
- 一个主文件只能有一个聚簇索引文件,但可以有多个非聚簇索引文件。
- 主索引通常是聚簇索引(但其索引项总数不一定和主文件中聚簇字段上不同值的数目相同,其和主文件存储块数目相同)。
- 辅助索引通常是非聚簇索引。
- 主索引/聚簇索引是能够决定记录存储位置的索引,而辅助索引/非聚簇索引则只能用于查询,指出已存储记录的位置。
必考内容
当索引项比较多时,可以对索引再建立索引,依此类推,形成多级索引。
B+ 树节点:
\(K_i\):
索引字段值
\(P_i\):
是指向 索引块或数据块或数据块中记录 的指针:
每个索引块的指针利用率(书上是说索引字段值的利用率)都在 \(50\%-100\%\) 之间(根节点可以不满足)。
索引字段值 \(x\) 在 \(K_{i-1}\le x<K_i\) 的由 \(P_i\) 指向(设 \(K_0=0\)),而 \(K_i\le x<K_{i+1}\) 的由 \(P_{i+1}\) 指向。
叶节点的 \(P_n\) 指向下一个叶节点。
共有 \(n-1\) 个索引字段值和 \(n\) 个指针。
B 树和 B+ 树比较:
B+ 树插入:
在原树的叶子节点中找到应该插入的位置:
当索引块满时,需要分裂,分裂后也要保证指针利用率不低于一半(平均分)。
将这 \(n\) 个搜索码值(叶结点中原有的 \(n-1\) 个值再加上新插入的值)分为两组,前 \(\lceil n/2\rceil\) 个放在原来的结点中,剩下的放在一个新结点中。
否则直接插入,如果插入位置在叶子索引块最左边(即小于该块的其他索引块,要修改父节点的值)。
分裂可能引发连锁反应,由叶结点直至根结点判断。
分裂后需要仔细调整索引键值及指针。
注意叶子结点之间链接关系的调整。
B+ 树删除:
桶:
表示能存储一条或多条记录的一个存储单位,一个桶可以是一个或者若干个连续的存储块。
散列函数:
\(K\) 表示所有搜索码值的集合,\(B\) 表示所有桶地址的集合,散列函数 \(h\) 是一个从 \(K\to B\) 的函数。
散列函数要满足分布是均匀的和随机的。
溢出桶:
如果一条记录必须插入桶 \(b\) 中,而桶 \(b\) 已满,系统会为桶 \(b\) 提供一个溢出桶,并将此记录插入到这个溢出桶中。
静态散列索引的缺点:
如果桶的数目 \(M\) 不变,过大则浪费,过小则将产生更多的溢出桶,增加散列索引检索的时间。
以下为两种动态散列索引️,考选择题,注意区别和名字。
可扩展散列索引:
有一个附加的间接层(指针数组),系统在访问桶本身之前必须先访问指针数组表。
指针数组能增长,其长度总是 \(2\) 的幂。因而数组每增长一次,桶的数目就翻倍。
多个桶可能共享一个数据块(即多个指针数组指向同一个数据库)。
参数 \(k\) 表示散列函数所可能使用的最多位数。
散列函数:
散列函数 \(h\) 为每个键计算出一个 \(k\) 位二进制序列(散列函数值)。
\(i\) 为散列函数当前已经使用到的最多位数。取散列函数值的前 \(i\) 位(散列前缀)为作为要插入桶的编号。
插入某个桶时发现已满,则需要扩展散列桶,进行分裂:
\(i\) 增 \(1\),重新散列该块的数据到两个块中。
其他的桶按照散列前缀重新指向对应数据块,会出现多个桶共享一个块的情况(因为在后面加 \(0/1\) 指向不变)。
缺点:
- 翻倍要处理的工作量很大。
- 桶数翻倍后,主存可能装不下。
- 可能产生大量空间浪费,因为一个数据块满了得翻倍,其他块可能都没有存多少数据。
线性散列索引:
桶数的选择:
保持与存储块所能容纳的记录总数成一个固定的比例,例如 \(80\%\)。超过此比例,则桶数仅增长 \(1\) 块。不超过这个比例时允许有溢出桶。
假定散列函数值的 \(i\) 位为桶数组项编号。现在要插入一个键值为 \(K\) 的记录:
通过散列函数得知要插入编号为 \(a_{1} a_{2} \ldots a_{i}\) 的桶中,即 \(a_{1} a_{2} \ldots a_{i}\) 是 $h(K) $ 的后 \(i\) 位。设 \(m=a_{1} a_{2} \ldots a_{i}\),\(n\) 为当前的桶数。
如果 \(m<n\),则编号为 \(m\) 的桶存在,并把记录存入该桶中。
如果 \(n \leq m<2^{i}\),那么桶还不存在,因此我们把记录存入桶 \(m-2^{{i}-1}\),也就是当我们把 \(a_{1}\)(它现在是 \(1\))改为 \(0\) 时对应的桶。
如果插入后不满足比例时,要分裂一个桶成两个,这个要分裂的桶的编号是这样确定的:
设当前 \(n\) 已增一,则分裂与 \(n\) 低位相同而最高位不同的那一个桶,即假设当前为 \(n=1a_2a_3\cdots a_i\),则就从 \(0a_2a_3\cdots a_i\) 对应的桶分裂而来。
因为当时有些记录是强行插入桶中的(上面 \(n \leq m<2^{i}\) 的情况,那时还没有 \(1a_2a_3\cdots a_i\) 这个桶,也得硬插),插入的桶号就是 \(0a_2a_3\cdots a_i\),现在分裂了,就可以插入对应正确的桶中了。
分裂只会影响分裂之后的两个桶的记录,其他保持不变。
当桶数超过 \(2^i\) 个桶时,则使 \(i\) 增 \(1\)。
查询优化:
构造具有最小查询执行代价的查询执行计划应当是系统的责任,这项工作叫作查询优化:
逻辑层优化:
优化关系代数操作执行次序,后面会详细讲。
物理层优化:
优化关系代数操作实现算法,存取路径与执行次序。
为每一个关系代数操作选择优化的执行例行程序,形成物理查询计划。
物理层优化的查询实现:
一次单一元组的一元操作(选择和投影):
整个关系的一元操作:
整个关系的二元操作:
集合上的操作:
\(\cup_{{S}}, \cap_{{S}},-{S}\)
包(允许重复的集合)上的操作:
\(\cup_{B}, \cap_{B},-{B}\)
笛卡尔积,连接:
\(\rm PRODUCT,JOIN\)
关系的物理存储相关的参数:
关系是存储在磁盘上的,磁盘是以磁盘块为操作单位,首先要被装载进内存(I/O 操作),然后再进行元组的处理。
\({T}_{{R}}\):
关系 R 的元组数目。
\({B}_{{R}}\):
关系 R 的磁盘块数目。
$ {M}$:
主存缓冲区的页数(主存每页容量等于一个磁盘块的容量)。
\({I}_{{R}}\):
关系 R 的每个元组的字节数。
\(b\):
每个磁盘块的字节数。
\[{B}_{{R\times S}}={T}_{{R}} {T}_{{S}}({I}_{{R}}+{I}_{{S}}) / {b}
\]
下面讨论各种连接操作的实现算法️:
- 因为 I/O 操作所需时间远大于内存操作,下面仅考虑 I/O 用时(次数)。
- 忽略写回结果所需时间(也就是说只考虑 I(input) 没有 O(output)),因为很难估计。
连接操作算法 \(P_1\)(主存利用率低):
/* I/O 操作 */
For i = 1 to BR
read i-th block of R // 执行 BR 次
For j = 1 to BS
read j-th block of S // 执行 BR * BS 次 /* 内存操作 */
For p = 1 to b/IR // 取元组,b/IR 表示一个磁盘块有多少个关系 R 的元组
read p-th record of R
For q = 1 to b/IS
read q-th record of S
if R.A 关系 S.B then
串接 p-th record of R和q-th record of S;
存入结果关系;</code></pre>
I/O 次数估计为 \(B_R + B_R\times B_S\)
连接操作的全主存实现算法 \(P_2\):
/* 算法假定内存大小 M >= BR + BS,即只需要读一遍关系 R 和 S 的磁盘块 */
For i = 1 to BR
read i-th block of R
For j = 1 to BS
read j-th block of S
/* 内存操作稍有不同,不是重点 */
I/O 次数估计为 \(B_R + B_S\)
连接操作的半主存实现算法 \(P_3\):
/* 算法假定内存大小min(BR,BS) < M < BR + BS,这里假设较小的是 BR,读一次 R 的磁盘块放内存,S 的每次读就处理,不用全部放入内存再处理 */
For i = 1 to BR
read i-th block of R
For j = 1 to BS //一次读入一块关系 S 的磁盘块
read j-th block of S
/* 内存操作稍有不同,不是重点 */
I/O 次数估计为 \(B_R + B_S\)
连接操作的大关系实现算法 \(P_4\)(将主存充分利用):
/* 把关系 R 划分为 BR/(M-2) 个子集合,每个子集合具有 M-2 块。令 MR 为 M-2 块容量的主存缓冲区,
* MS 为 1 块容量的 S 的主存缓冲区,还有 1 块作为输出缓冲区。
*/
For i = 1 to BR/(M-2) //一次读入M-2块
read i-th Sub-set of R into MR // 执行 BR/(M-2) 次,总共 BR 次 I/O
For j = 1 to BS //一次读入一块
read j-th block of S into MS // 执行 BR/(M-2) * BS 次
For p = 1 to (M-2)b/IR
/* 内存操作稍有不同,不是重点 */
I/O 次数估计为 \(B_S(B_R /(M-2)) + B_R\)
迭代器不考。
关系 \(R\) 数据读取:
\(B(R)\) 是 \(R\) 的存储块(磁盘块)数目
\(T(R)\) 是 \(R\) 的元组数目
忽略写回代价
聚簇关系:
指关系的元组集中存放(一个块中仅是一个关系中的元组)。
TableScan(R) 为表空间扫描算法:
扫描结果未排序 \(B(R)\)
SortTableScan(R):
扫描结果排序 \(B(R)~or~ 3B(R)\)
内存装得下 \(R\) 就只需要 \(B(R)\),否则需要 \(3B(R)\):
- 先读一遍 \(R\),做第一趟排序。
- 写回磁盘一次,因为还需要第二趟排序。
- 再多路读 \(R\),做归并排序。
- 不考虑写回,上面共 \(3B(R)\)。
IndexScan(R) 为索引扫描算法:
扫描结果未排序 \(B(R)\)
SortIndexScan(R):
扫描结果排序 \(B(R)~or~ 3B(R)\)
非聚簇关系:
关系的元组不一定集中存放(一个块中不仅是一个关系中的元组)。最坏情况下就是相邻的元组都不在同一磁盘块,每次都要读一个新磁盘。
去重复 \(\&(R)\):
分组聚集 \(\gamma _L(R)\)
需要在内存中保存所有的分组,保存每个分组上的聚集信息。
建立不同的内存数据结构(排序结构/散列结构/B+ 树),来保存之前处理过的数据,以便快速处理整个关系上的操作。
算法复杂性为 \(B(R)\)
应用条件为所有分组的数量应能在内存中完整保存。
集合或者包上的二元运算:
连接操作实现算法 \(P_4\) 的改进:
选择条件中有涉及到索引属性时,可以使用索引,辅助快速检索。
聚簇和非聚簇索引,使用时其效率是不一样的。
案例分析️:
假设 \(B(R)=1000, T(R)=20000\),即 \(R\) 有 \(20000\) 个元组存放到 \(1000\) 个块中。
\(a\) 是 \(R\) 的一个属性,在 \(a\) 上有一个索引,并且考虑 \(\sigma_{a=0}(R)\) 操作:
如果 \(R\) 是聚簇的,且不使用索引,查询代价 \(=~1000\) 个 I/O。
一个块中仅是一个关系中的元组,所以最多遍历 \(1000\) 次磁盘块就能找到所以元组。
如果 \(R\) 不是聚簇的,且不使用索引,查询代价 \(=~20000\) 个 I/O。
一个块中不仅是一个关系中的元组,可能含有其他数据,必须通过元组的指针进行遍历,所以要访问 \(20000\) 个元组,考虑最坏情况,相邻元组都不在同一个块,那么就要访问 \(20000\) 个块。
\(V(A, R)\) 表示 \(R\) 中属性 \(A\) 出现不同值的数目,即 \(\Pi_A(R)\) 的数目。
PPT 中 \(A,R\) 的顺序时常发生改变,询问老师说都可以。书上是属性在前,关系名在后。为了统一写法,下面全部采用书本的形式,因此和 PPT 几乎都是相反的。
如果 \(V(a,R)=100\) 且索引是聚簇的,查询代价估计 \(=1000/100=10\) 个 I/O。
有索引的帮助,可以快速定位到这个元组,再加上是聚簇的,所以相同值的也是相邻的。
因为有 \(100\) 个不同的值,所以在最坏情况下平均分散到 \(10\) 个块里面。
如果 \(V(a,R)=100\) 且索引是非聚簇的,查询代价 \(=~20000/100=200\) 个 I/O。
如果 \(V(a,R)=20000\),即 \(a\) 是关键字,主键索引,查询代价 \(=~20000/20000=1\) 个 I/O,不管是否是聚簇的。
基于有序索引的连接算法(Zig-Zag 连接算法):
实验做,考试老师说不会考。
基本思路:
第一趟:
划分子集,并使子集具有某种特性,如有序或相同散列值等。
第二趟:
处理全局性内容的操作,形成结果关系。如多子集间的归并排序,相同散列值子集的操作等。
内排序:
待排序的数据可一次性地装入内存中,即排序者可以完整地看到和操纵所有数据。内存中数据的排序算法有插入排序、选择排序、冒泡排序等。
外排序:
待排序的数据不能一次性装入内存,即排序者不能一次完整地看到和操纵所有数据,需要将数据分批装入内存分批处理的排序问题。
多路归并的过程(仅简单分析):
设内存块数为 \(B_{memory}\),待排序数据块数为 \(B_{problem}\)。
第一趟划分子集并排序,这里要求每个子集的块数要小于等于内存块数 \(B_{memory}\),I/O 次数为 \(2B_{problem}\)。
第二趟各子集间的归并排序,这里要求子集的数目要小于内存块数 \(B_{memory}\),I/O 次数为 \(2B_{problem}\)。
这两个限制说明大数据集块数 \(\le B^2_{memory}\)。
同时内存的块中得有一块用来输出(第一趟不需要输出块),一块用来比较(老师说可以没有)。
算法的效率:
读写磁盘块的次数,即 I/O 数 \(=4B_{problem}\)。
必考,估计选择填空题️。
通过语法树,表达和分析关系代数表达式:
从叶子到树根执行。
策略:
尽可能提前选择和投影(相当于这两个运算在语法树上尽量下放):
可使中间结果变小,节省几个数量级的执行时间。
选择与投影尽量一起做:
投影或选择后,下一个投影或选择操作通过中间结果进行操作,这样就不需要再从磁盘读数据。
投影与其前后的二元运算结合:
通过投影,去掉关系的无关属性(不参与二元运算的属性),可以避免多次扫描整个关系。
PPT 似乎只使用了以上三个策略,后面 MOOC 中有使用,还是按 PPT 来吧。
笛卡尔积转连接运算(把选择与其前的笛卡尔积合并成一个连接):
当 \(R\times S\) 前有选择运算且其中存在条件是 \(R,S\) 属性间比较的运算时,可将其转化为连接运算可节省时间。
执行连接运算前对关系做适当预处理:
文件排序、建立临时索引等,可使两关系公共值高效联接。
找出表达式里的公共子表达式:
若公共子表达式结果不大,则预先计算,以后可读入此结果,节时多,尤当视图情况下有用。
关系代数操作次序交换的等价性:
\(L_1\),连接与积(并交)的交换律:
设 \(E_1, E_2\) 是关系代数表达式,\(F\) 是 \(E_1, E_2\) 中属性的附加限制条件,则有:
\[\begin{aligned}
E_{1} \bowtie E_{2} &\equiv E_{2} \bowtie E_{1} \\
E_{1} \underset{F}\bowtie E_{2} &\equiv E_{2} \underset{F}\bowtie E_{1} \\
E_{1} \times E_{2} &\equiv E_{2} \times E_{1}
\end{aligned}
\]
通过交换,可以将 \(E_1, E_2,\cdots ,E_n\) 中结果小集合小的放入内存,进行连接或者积操作,可以减小中间结果。
\(L_2\),连接与积(并交)的结合律:
若 \({E}_{1}, {E}_{2}, {E}_{3}\) 是关系代数表达式,\({F}_{1}, {~F}_{2}\) 是条件,则有:
\[(E_{1} \underset{F_1}\bowtie E_{2}) \underset{F_2}\bowtie E_{3} \equiv E_{1} \underset{F_1}\bowtie (E_{2}\underset{F_2}\bowtie E_{3})
\\(E_{1} \bowtie E_{2}) \bowtie E_{3} \equiv E_{1} \bowtie(E_{2} \bowtie E_{3})
\\(E_{1} \times E_{2}) \times E_{3} \equiv E_{1} \times(E_{2} \times E_{3})
\]
结合律和交换律说明结果与运算顺序无关,可以将 \(E_1, E_2,\cdots ,E_n\) 中结果集合排序,升序放入内存,进行连接或者积操作,可以减小中间结果。
\(L_3\),投影串接律(双向使用):
设属性集 \(\left\{A_{1},\cdots, A_{n}\right\} \subseteq\left\{B_{1}, \cdots, B_{m}\right\}\),$ E$ 是表达式,则有:
\[\Pi_{A_{1}, \cdots, A_{n}}\left(\Pi_{B_{1}, \cdots, B_{m}}(E)\right) \equiv \Pi_{A_{1}, \cdots, A_{n}}(E)
\]
从左往右使用,两遍扫描变为一遍扫描,减少 IO 次数。
从右到左使用,可以用于扩充,便于将投影在语法树中下放,减少无关属性。
\(L_4\),选择串接律(双向使用):
若 \(E\) 是关系代数表达式,\({F}_{1}, {~F}_{2}\) 是条件,则有:
\[\sigma_{F_{1}}\left(\sigma_{F_{2}}(E)\right) \equiv \sigma_{F_{1} \wedge F_{2}}(E)
\]
理由其实同上,只是扩充的时候是选择下放。
\(L_5\),选择和投影的交换律:
\[\Pi_{A_{1}, \cdots, A_{n}}\left(\sigma_{F}(E)\right) \equiv \sigma_{F}\left(\Pi_{A_{1},\cdots, A_{n}}(E)\right)
\]
\[\Pi_{A_{1}, \cdots, A_{n}}\left(\sigma_{F}(E)\right) \equiv \Pi_{A_{1}, \cdots, A_{n}}\left(\sigma_{F}\left(\pi_{A_{1},\cdots, A_{n}, B_{1}, \cdots, B_{m}}(E)\right)\right)
\]
\(L_6\),选择和积的交换律:
设 \({E}_{1}, {E}_{2}\) 是关系代数表达式:
\[\sigma_{F}\left(E_{1} \times E_{2}\right) \equiv \sigma_{F}\left(E_{1}\right) \times E_{2}
\]
\[\sigma_{F}\left(E_{1} \times E_{2}\right) \equiv \sigma_{F_{1}}\left(E_{1}\right) \times \sigma_{F_{2}}\left(E_{2}\right)
\]
\[\sigma_{F}\left(E_{1} \times E_{2}\right) \equiv \sigma_{F_{2}}\left(\sigma_{F_{1}}\left(E_{1}\right) \times E_{2}\right)
\]
\(L_7\),投影和积的交换律:
设 \(E_{1}, E_{2}\) 为两关系代数表达式,\(A_{1}, \cdots, A_{n}\) 是出现在 \(E_{1}\) 或 \(E_{2}\) 中的一些属性,其中 \({B}_{1}, \cdots, {B}_{{m}}\) 出现在 \({E}_{1}\) 中,剩余的属性 \({C}_{1}, \cdots, {C}_{{k}}\) 出现在 \({E}_{2}\) 中,则有:
\[\Pi_{A_{1}, \cdots, A_{n}}\left(E_{1} \times E_{2}\right) \equiv \Pi_{B_{1}, \cdots, B_{m}}\left(E_{1}\right) \times \Pi_{C_{1}, \cdots, C_{k}}\left(E_{2}\right)
\]
\(L_8\),选择和并的交换律:
设关系代数表达式 \(E=E_{1} \cup E_{2}, F\) 是条件,则有:
\[\sigma_{F}\left(E_{1} \cup E_{2}\right) \equiv \sigma_{F}\left(E_{1}\right) \cup \sigma_{F}\left(E_{2}\right)
\]
此定理要求 \({E}_{1}, {E}_{2}\) 是并相容的。
\(L_9\),选择和差的交换律:
设关系代数表达式 \(E=E_{1}-E_{2}, F\) 是条件,则有:
\[\sigma_{F}\left(E_{1}-E_{2}\right) \equiv \sigma_{F}\left(E_{1}\right)-\sigma_{F}\left(E_{2}\right)
\]
\(L_{10}\),投影和并的交换律:
设关系代数表达式 \(E=E_{1} \cup E_{2}, A_{1}, \cdots, A_{n}\) 是 \(E\) 中的一些属性,则有:
\[\Pi_{A_{1}, \cdots, A_{n}}\left(E_{1} \cup E_{2}\right) \equiv \Pi_{A_{1}, \cdots, A_{n}}\left(E_{1}\right) \cup \pi_{A_{1}, \cdots, A_{n}}\left(E_{2}\right)
\]
因为投影会去重,投影和差的交换律是不成立的,即:
\[\Pi_{A_{1}, \cdots, A_{n}}\left(E_{1} - E_{2}\right) \not\equiv \Pi_{A_{1}, \cdots, A_{n}}\left(E_{1}\right) - \pi_{A_{1}, \cdots, A_{n}}\left(E_{2}\right)
\]一个大概的例子:
假设 \(E_1\) 只有 \(3\) 个元组,在属性 \({A_{1}, \cdots, A_{n}}\) 上都相等,\(E_2\) 只有一个元组,与 \(E_1\) 的 \(3\) 个元组之一相等。
先差后投影:
\(E_1-E_2\) 有两个元组,去重后有 \(1\) 个元组。
先投影后差:
投影后 \(E_1,E_2\) 都只有一个元组,差运算后结果为空,与前者不等价。
优化算法执行流程:
依据选择串接律 \(\sigma_{F_{1}}\left(\sigma_{F_{2}}(E)\right) \equiv \sigma_{F_{1} \wedge F_{2}}(E)\),对于右边形式的关系表达式,转为左边串接形式。
对每个选择和投影,依据定理 \(L_4\) 至 \(L_9\),尽可能把它下放(如果一个投影是对某表达式所有属性进行的,相当于 select *
,则去掉)
依据选择串接律和投影串接律把串接的选择和投影组合为单个选择、单个投影。
笔者感觉以上修改要多次使用,直到不能再应用为止。
对修改后的语法树,将其内结点按以下方式分组:
语法树执行顺序为:
以每组结点为一步,后代先执行,从叶子执行到根。
统计信息:
\(T_R\) 或 \(T(R)\):
关系 \(R\) 的元组数目。
\(V(A, R)\):
\(R\) 中属性 \(A\) 出现不同值的数目,即 \(\Pi_A(R)\) 的数目。
PPT 中 \(A,R\) 的顺序时常发生改变,询问老师说都可以。书上是属性在前,关系名在后。为了统一写法,下面全部采用书本的形式,因此和 PPT 几乎都是相反的。
估计的目标:
给定一个表达式 \(E\),如何估计 \(E\) 的元组数目 \(T(E)\)。
估算 \(\pi_{{A}}({R})\) 的大小:
PPT:
\({T}(\pi_{{A}}({R}))={T}({R})\)
笔者认为应该是:
\({T}(\pi_{{A}}({R}))=V(A,R)\)
投影运算并末减少行数,但可能有效地减少了存储结果关系的块数(每个元组所占的大小减少)。
估算选择运算 \(S= \sigma_{A=c}(R)\) 的大小:
严谨:
\({T}({S})\) 介于 \([0,{T}({R})-{V}({A}, {R})+1]\) 之间
原因分析:
- 最小值为 \(0\),因为可能关系 \(R\) 中的 \(A\) 属性不存在值为 \(c\) 的元组。
- 最大值为 \({T}({R})-{V}({A}, {R})+1\),即关系 \(R\) 中的 \(A\) 属性值不为 \(c\) 的元组数目都是 \(1\),共有 \(V(A,R)\)。
估计:
\({T}({S})={T}({R}) / {V}({A}, {R})\),即 \(A\) 属性不同值的元组数相等。
暴力估计:
不知道 \(V(R,A)\) 时,默认为 \(10\),即 \(T(S) = T(R)/10\)。
估算选择运算 \({S}=\sigma_{{A}<{c}}({R})\) 的大小:
严谨:
\({T}({S})\) 介于 \(0\) to \({T}({R})\) 之间
分析:
- 最小值 \(0\) 即不存在这样的元组。
- 最多 \(T(R)\) 即所有元组都满足条件。
估计:
\({T}({S})={T}({R}) / 2\),应有一半的元组满足条件。
实际常用估计:
\({T}({S})={T}({R}) / 3\)
估算选择运算 \({S}=\sigma_{{A}=10 {~and~} B<20}({R})\) 的大小
估计:
\({T}({S})={T}({R}) /({V}({R}, {A})^{*} 3)\)
分析:
- \(\sigma_{{A=10}~and~ B<20}(R)=\sigma_{B<20}(\sigma_{A=10}(R))\)
- \(A=10\),得出 \(T(S)=T(R) / V(R, A)\)
- \({B}<20\),得出 \({T}({S})={T}({S}) / 3\)
估算选择运算 \(S = \sigma_{C_1~ or ~C_2}(R)\) 的大小:
估计:
\(\begin{aligned}{T}({S})={n}(1-(1-\frac{{m}_{1}}{{n}})(1-\frac{{m}_{2}}{{n}}))\end{aligned}\)
分析:
- \(R\) 有 \(n\) 个元组,其中有 \(m_{1}\) 个满足 \(C_1\),有 \(m_{2}\) 个满足 \(C_2\)。
- \(\begin{aligned}(1-\frac{{m}_{1}}{{n}})\end{aligned}\) 是不满足 \(C_1\) 的那些元组,\(\begin{aligned}(1-\frac{{m}_{2}}{{n}})\end{aligned}\) 是不满足 \(C_2\) 的那些元组。
- 两数之积是不满足条件的元组概率(这里并不严谨),\(1\) 减去这个积就是满足条件元组出现的概率。
前一种类型的举例,估计选择运算 \(S=\sigma_{A=10~or~ B<20} (R)\) 的大小:
分析:
\[\begin{gathered}
n=T(R)=10000\\ V(R, A)=50 \\
m_{1}=T(R) / V(R, A)=10000 / 50=200
\\m_{2}=T(R) / 3=10000 / 3 \approx 3333
\end{gathered}
\]即有 \(m_{1}\) 个满足 \(C_1\),有 \(m_{2}\) 个满足 \(C_ 2\),\(\begin{aligned}(1-\frac{{m}_{1}}{{n}})(1-\frac{{m}_{2}}{{n}})\end{aligned}\) 是不满足这个条件的元组的概率,计算如下:
\[T(S)=10000^{\star}(1-(1-200 / 10000)(1-3333 / 10000)) \approx 3466
\]也可以简单估计为 \(T(S)=T(R) / 3=10000 / 3 \approx 3333\)
估算连接运算 \(S'=R(X, Y) \text{~Natural Join~}S(Y, Z)\) 的大小:
估计:
\(\begin{aligned}{T}({S'})=\frac{{T(R)}{T(S)} }{\max({V}({Y}, {R}), {V}({Y}, {S}))}\end{aligned}\)
分析:
假定 \(V(Y, R)\ge V(Y, S), R\) 中元组 \(r\) 和 \(S\) 中元组有相同 \(Y\) 值的概率 \(=1 / V(Y,R)\)
假定 \(V(Y, R)<V(Y, S), R\) 中元组 \(r\) 和 \(S\) 中元组有相同 \(Y\) 值的概率 \(=1 / V(Y, S)\)
则笛卡尔积后的关系在 \(Y\) 上相等的概率 \(=1 / \max (V(R, Y), V(S, Y))\)
笔者尝试解释:
首先假设数据是均匀分布,即假设一个关系 \(U\) 的 \(Y\) 属性中,具有相同属性值的元组数均为 \(x\) 个,这样总元组数是 \(x\times V(Y,U)\)。
\(R\) 关系的总元组数为 \(x_R\times V(Y,R)\),\(S\) 关系的总元组数为 \(x_S\times V(Y,S)\)。
假设 \(R.Y\subseteq S.Y,V(Y, R)<V(Y,S)\),即 \(R\) 的 \(Y\) 属性值在 \(S\) 中均存在。这使得 \(R\) 的每个元组,都能等值连接 \(x_S\) 个 \(S\) 的元组。
所以 \(R\times S\) 中,满足 \(R.Y=S.Y\) 的元组数为 \(x_R\times V(Y,R)\times x_S\)。然后除以总元组数即可得到概率(其实分子就是估计值 \(T(S')\)):
\[\frac{x_R\times V(Y,R)\times x_S}{x_R\times V(Y,R)\times x_S \times V(Y,S)}=\frac{1}{V(Y,S)}
\]PPT 上的例子 \(T(R)=10000, T(S)=50000, V(R, Y)=500, V(S, Y)=1000\),
估计为 \(T(S’)=10000 * 50000 / 1000=500000\)
事务的定义:
事务的宏观性(应用程序员看到的事务):
一个存取或改变数据库内容的程序的一次执行,或者说一条或多条 SQL 语句的一次执行被看作一个事务。
事务一般是由应用程序员提出,因此有开始和结束,结束前需要提交或撤消(通过 commit 或 rollback 确认的)。
事务的微观性(DBMS 看到的事务):
对数据库的一系列基本操作(读、写)的一个整体性执行。
事务的并发执行:
多个事务从宏观上看是并行执行的,但其微观上的基本操作(读、写)则可以是交叉执行的。
并发控制就是通过事务微观交错执行次序的正确安排,保证事务宏观的独立性、完整性和正确性。
事务的特性(ACID 特性️):
原子性(atomicity):
事务的所有操作在数据库中要么全部正确反映出来,要么完全不反映。
一致性(consistency):
保证事务的操作状态是正确的,不能出现「丢失修改,不可重复读,脏读」三类错误,由隔离性保证。
丢失修改:
操作顺序是 \(T_1\) 读,\(T_2\) 读,\(T_1\) 写,\(T_2\) 写。\(T_1\) 的修改就被 \(T_2\) 所覆盖了,即丢失了修改。
不可重复读(理应能够重复读,但是现在不能重复读了,同一事务两次读期间未作修改,但读到的内容却不同):
操作顺序是 \(T_1\) 读,\(T_2\) 读,\(T_2\) 写,\(T_1\) 再读,发现与第一次读的数据不一致。
脏读:
操作顺序是 \(T_2\) 读,\(T_2\) 写,\(T_1\) 读,\(T_2\) 回滚,此时 \(T_1\) 读到的是其他事务未提交的数据,回滚后这个数据已经无效了,称为脏数据。
隔离性(isolation):
尽管多个事务可能并发执行,但系统保证,对于任何一对事务 \(T_{{i}}\) 和 \(T_{{j}}\),在 \(T_{i}\) 看来,\(T_{j}\) 或者在 \(T_{{i}}\) 开始之前已经完成执行,或者在 \(T_{{i}}\) 完成之后开始执行。因此,每个事务都感觉不到系统中有其他事务在并发地执行。
持久性(durability):
一个事务成功完成后,它对数据库的改变必须是永久的,即使出现系统故障。
可以说具有 ACID 特性的若干数据库基本操作的组合体被称为事务。
事务调度概念:
一组事务的基本步骤(读、写、其他控制操作如加锁、解锁等)的一种执行顺序称为对这组事务的一个调度。
并发调度的正确性:
并发调度下所得到的新数据库结果与分别串行地运行这些事务所得的新数据库完全一致,调度才是正确的,是结果意义上的正确。
可串行性:
如果不管数据库初始状态如何,一个调度对数据库状态的影响都和某个串行调度相同,则我们说这个调度是可串行化的或具有可串行性。
可串行化调度一定是正确的并行调度,但正确的并行调度,却未必都是可串行化的调度。
因为并行调度的正确性是指内容上结果正确性(结果对就对了),而可串行性是指形式上结果正确性,便于操作。
一种简单的事务调度的标记模型:
\(r_T(A)\):
事务 \(T\) 读 \(A\)。
\(w_T(A)\):
事务 \(T\) 写 \(A\)。
冲突:
如果调度中两个动作的顺序交换,若涉及的事务中至少有一个事务的行为会改变,则称这两个动作冲突。
有冲突的两个操作是不能交换次序的,没有冲突的两个事务是可交换的
几种冲突的情况:
同一事务的任何两个操作都是冲突的:
\[r_{i}(X) ; w_{i}(Y) \quad w_{i}(X) ; r_{i}(Y)
\]
不同事务对同一元素的两个写操作是冲突的:
\[w_{i}(X) ; w_{j}(X)
\]
不同事务对同一元素的一读一写操作是冲突的:
\[w_{i}(X) ; r_{j}(X) \quad r_{i}(X) ; w_{j}(X)
\]
冲突可串行性:
一个调度,如果通过交换相邻两个无冲突的操作能够转换到某一个串行的调度(注意不是可串行),则称此调度为冲突可串行化的调度。
冲突可串行性是比可串行性更严格的概念,即冲突可串行性是可串行性的充分不必要条件。
例子:
\({w}_{1}({Y}) ; {w}_{2}({Y}) ; {w}_{2}({X}) ; {w}_{1}({X}) ; {w}_{3}({X})\) 是一个可串行化的调度,因为它与串行调度 \({w}_{1}({Y}) ; {w}_{1}({X}) ; {w}_{2}({Y}) ; {w}_{2}({X}) ; {w}_{3}({X})\) 的结果等价。
但是它相邻动作都是冲突的,不能交换形成串行调度,所以不是冲突可串行性的调度。
冲突可串行性判别算法:
例子:
与边上的元素无关,只要存在环就是不满足冲突可串行的。
锁的类型:
排他锁 X:
只有一个事务能读、写,其他任何事务都不能读、写。
共享锁 S:
所有事务都可以读,但任何事务都不能写。
更新锁 U:
初始读,以后可升级为写。
增量锁 I(后面不涉及):
增量更新(例如 \(A=A+x\))区分增量更新和其他类型的更新。
相容性矩阵:
当某事务对一数据对象持有一种锁时,另一事务再申请对该对象加某一类型的锁,是允许(填「是」)还是不允许(填「否」)。
有更新锁的情况:
申请锁
申请锁
申请锁
共享锁 S
排他锁 X
更新锁 U
持有锁的模式
共享锁 S
是
否
是
持有锁的模式
排他锁 X
否
否
否
持有锁的模式
更新锁 U
否
否
否
锁协议:
️重难点,PPT 更改了很多次,也特意问了老师和同学,希望下面理解是正确的,考试大题问使用哪种锁协议,并说明理由。
背景:
一开始并发控制的时候,读写完全没有锁,如果不是可串行化调度就会出错。
\(0\) 级协议:
所有写操作都要加排他锁,但排他锁是写完就释放,而不是直到提交事务才释放。
注意在排他锁期间是可以读的,读操作并没有要求上锁,也就不会因为申请有关读的锁而阻塞。
不能防止丢失修改,不可重复读,脏读三种不一致错误,完全没用属于是。
分析:
丢失修改:
不可重复读:
脏读:
\(1\) 级协议:
所有写操作都要加排他锁,排他锁直到提交事务才释放。
可以防止丢失修改,因为在别人不能在你提交前进行修改(有写排他锁),这样你的修改将可以一直保留到提交。
不能防止不可重复读和脏读,理由完全同 \(0\) 级协议的图,因为对读没有任何限制,你排他锁关我读操作什么事。
\(2\) 级协议:
所有写操作同 \(1\) 级协议,读操作要加共享锁,但共享锁是读完就释放,而不是直到提交事务才释放。
可以防止丢失修改,理由同 \(1\) 级协议。
可以防止脏读,因为读操作需要申请共享锁,你能申请到共享锁,是因为这个元素没有被上排他锁,所以就不会有对该元素的写操作,那么即使有回滚也和该元素无关。
不能防止不可重复读,理由见下图:
\(3\) 级协议:
所有写操作同 \(1\) 级协议,读操作要加共享锁,直到提交事务才释放。
防止丢失修改,不可重复读,脏读。
不可重复读现象不会发生,因为不会出现上图的共享锁释放后还可以被别人修改,且我再读的现象。
封锁粒度:
定义:
封锁粒度是指封锁数据对象的大小。
粒度单位:
属性值 \(<\) 元组 \(<\) 元组集合 \(<\) 整个关系 \(<\) 整个 DB
由前往后:
并发度小,封锁开销小。
两段封锁协议:
每个事务分两个阶段提出加锁和解锁申请。
等价于 PPT 中的加锁段中不能有解锁操作,解锁段中不能有加锁操作。
每个事务中所有封锁请求先于任何一个解锁请求。
可以保证冲突可串行性的,但是可能产生死锁。
不用锁实现并发控制。
时间戳定义:
时间戳是一种基于时间的标志,将某一时刻转换成的一个数值,具有唯一性和递增性。
基本思想:
事务 \(T\) 启动时,系统将该时刻作为 \(T\) 的时间戳。
时间戳可以表征一系列事务执行的先后次序,时间戳小的事务先执行,时间戳大的事务后执行(但是实际上是交叉执行的)。
强制事务调度等价于一个特定顺序(即时间戳升序)的串行调度。
对于每个动作都要判断是否存在时间戳上的冲突(保证时间戳小的先操作,大的后操作):
如无冲突,予以执行;
如有冲突,则撤销这个动作对应的事务,并重启该事务,此时该事务获得了一个更大的时间戳,对应的动作位置会发生改变,重新做冲突判断。
回滚 = 撤销 + 重启
冲突类型:
简单调度:
对 DB 中的每个数据元素 \(x\),系统保留其上的最大时间戳:
\(RT(x)\):
读过 \(x\) 的事务中最大的时间戳。
\(WT(x)\):
写过 \(x\) 的事务中最大的时间戳。
事务的时间戳 \(TS(T)\)
读 - 写并发(对应解决冲突类型 \(1\)):
若 \(T\) 事务读 \(x\),则比较 \(TS(T)\) 与 \(WT(x)\):
若 \(T\) 事务写 \(x\),则比较 \(TS(T)\) 与 \(RT(x)\),其实同时要考虑「写 - 写」并发:
若 \(TS(T) \ge RT(x)\)(\(T\) 后进行),则允许 \(T\) 操作,更改 \(WT(x)\) 为 \(TS(T)\)
这里与 PPT 不同,笔者认为 \(TS(T)\) 不会比 \(WT(x)\) 小(即前者一定更大),否则会发生写 - 写冲突。
否则有冲突,\(T\) 回滚。
写 - 写并发(对应解决冲突类型 \(2\)):
以上简单调度会产生两种不一致错误(即时间戳并没有对这两种错误加以限制,只是消除了不可重复读错误):
脏读:
事务 \(T\) 读取了事务 \(U\) 没提交的数据,在 \(U\) 回滚后就变成了脏数据。
类似丢失修改的错误:
\(T\) 的写在托马斯写规则下,会被忽略,但是 \(U\) 终止了,导致谁都没用写成 \(x\)。这种情况下理应保留 \(T\) 的写。
优化后的时间戳调度:
\(WT(x),RT(x),TS(T)\) 与简单调度定义相同。
增加提交位 \(C(x)\):
对来自事务 \(T\) 的读写请求,调度器可以:
若 \(T\) 事务读 \(x\),则比较 \(TS(T)\) 与 \(WT(x)\):
若 \(TS(T) \ge WT(x)\)(\(T\) 后进行),理论上应该允许 \(T\) 操作,但要判断最近的写是否提交:
\(C(x)=1\),说明已提交,不会回滚,同意请求,可以放心的读,不会是脏数据。更改 \(RT(x)\) 为 \(\max\{RT(x),TS(T)\}\)
推迟 \(T\) 直到 \(C(x)=1\) 或写 \(x\) 的事务终止
前一种情况仍可以放心读,后一种情况就不能读了,需要回滚。
否则有冲突,\(T\) 回滚。
若 \(T\) 事务写 \(x\),则比较 \(TS(T),RT(x),WT(x)\),即同时要考虑「写 - 读」和 「写 - 写」并发:
若 \(TS(T) \ge RT(x)\) 且 \(TS(T) \ge WT(x)\),则允许 \(T\) 操作,更改 \(WT(x)\) 为 \(TS(T)\)。
如果 \(TS(T)\ge RT(x)\),但是 \(TS(T)<WT(x)\),就要判断是否已提交
\(C(x)=1\),那么前一个 \(x\) 的写已提交,则忽略 \(T\) 的写(托马斯写规则)。
否则推迟 \(T\) 直到 \(C(x)=1\) 或写 \(x\) 的事务终止。
如果是前一种情况就会忽略 \(T\) 的写。后一种情况比较复杂,我觉得不能简单地通过 \(T\) 的写请求:
- 假设现在有事务 \(S_1,S_2,T\),时间戳排序为 \(T<S_1<S_2\),写 \(x\) 的顺序为 \(S_1,S_2,T\)。此时 \(WT(x)=TS(S_2)\),且 \(S_1\) 已提交,\(S_2\) 未提交。
- \(T\) 的写请求会进入推迟判断,即使最后 \(S_2\) 回滚了,但是由于 \(S_1\) 的写已经提交,也应该忽略 \(T\) 的写。
- PPT 对这种情况没有处理办法(如何感知 \(S_1\) 的存在),所以这只是笔者的一种疑惑。
否则有冲突,\(T\) 回滚。
假设调度器收到提交 \(T\) 的请求:
假设调度器收到终止 \(T\) 的请求:
任何等待 \(T\) 所写元素 \(x\) 的事务判断这一动作在 \(T\) 的写被终止后是否合法。
具体怎么判断就像上面说的一样还缺少一些信息作为判断依据。
基本思想:
事务在启动时刻被赋予唯一的时间戳,以示其启动顺序。
RS(T):
事务 T 读数据的集合。
WS(T):
事务 T 写数据的集合。
事务分三个阶段进行:
读阶段:
事务从数据库中读取读集合中的所有元素,在其局部地址空间计算它将要写的所有值。
有效性确认阶段:
调度器通过比较该事务与其它事务的读写集合来确认该事务的有效性。
写阶段:
通过有效性确认后,该事务立刻往数据库中写入其写集合中元素的值(这个过程要判断「读 - 写」和「写 - 写」冲突)。
并发事务串行的顺序即事务有效性确认的顺序。
集合定义:
START 集合:
VAL 集合:
FIN 集合:
有效性确认规则
读 - 写并发:
对于所有已经过有效性确认,且在 T 开始前没有完成的 U,即对于满足 FIN(U)>START(T) 的 U,检测 RS(T) \(\cap\) WS(U) 是否为空:
写 - 写并发:
对于所有已经过有效性确认,且在 T 有效性确认前没有完成的 U,即对于满足 FIN(U)>VAL(T) 的 U,检测 WS(T) \(\cap\) WS(U) 是否为空:
例子:
故障类型:
事务故障:
某一个程序(事务)自身运行错误所引起的故障,影响该程序(事务)本身,只需要撤销事务和重做事务来进行恢复。
系统故障:
由于掉电、非正常关机等所引起的故障。影响正在运行的事务以及数据库缓冲区(数据库缓冲区涉及正在运行和已经运行的事务)。
介质故障:
由于介质(数据库)损坏等所引起的故障。影响是全面的,既影响内存中的数据, 又影响介质中存储的数据。
故障会破坏事务的原子性,一致性和持久性。
隔离性因为故障发生后不能执行事务,事务肯定是越少越不容易破坏隔离性。
故障恢复意义:
把 DB 由当前不正确状态恢复到已知正确的某一状态。
需要保证事务的:
原子性:
事务的所有操作,要么全都执行,要么全都不执行。
持久性:
已提交的事务对数据库产生的影响是持久的(缓冲区内容保证写回磁盘),未提交的事务对数据库不应有影响(缓冲区内容不能影响磁盘)。
运行日志:
运行日志是 DBMS 维护的一个文件,该文件以流水方式记录了每一个事务对数据库的每一次操作及操作顺序。
MOOC 上题目说「日志文件是用于记录对数据的所有更新操作」。
运行日志直接写入介质存储上,会保持正确性。
当事务对数据库进行操作时,先写运行日志。写成功后,再与数据库缓冲区进行信息交换。
保存信息:
事务故障的恢复:
事务故障可通过重做事务(Redo)和撤消事务(Undo)来恢复。
重做事务可保证已提交事务的持久性,而撤销事务则消除未提交事务的影响。
已提交事务的写数据,可能只是放在缓冲区中,还没有写入磁盘,当发生故障时将丢失修改。所以必须通过 Redo 重新把修改写磁盘。
DBMS 在运行日志中定期设置及更新检查点(checkpoint):
通过副本实现介质故障恢复:
事务和缓冲区的操作:
READ(X,t):
将元素 X 读到事务的局部变量 t 中。
WRITE(X,t):
将事务局部变量 t 写回元素 X。
INPUT(X):
将元素 X 从磁盘读入到内存缓冲区中。
OUTPUT(X):
将元素 X 从缓冲区写回到磁盘中。
COMMIT:
事务提交。
ABORT:
事务撤销。
缓冲区策略️:
Force:
缓冲区中的数据最晚在 commit 的时候写入磁盘。
No Force:
缓冲区中的数据可以一直保留,在 commit 之后过一段时间再写入磁盘。
如果在系统崩溃的时候还没写入到磁盘,需要 Redo。
Steal:
允许在事务 commit 之前把缓冲区中的数据写入磁盘(先偷偷写一点)。
此时若系统在 commit 之前崩溃时,已经有数据写入到磁盘了,要恢复到崩溃前的状态,需要 Undo。
No Steal:
不允许在事务 commit 之前把缓冲区中的数据写入磁盘。
Steal/No Steal 关心的缓冲区的数据最早什么时候开始写磁盘。
Force/No Force 关心的缓冲区的数据最晚什么时候要写回磁盘。
各种搭配的效率和策略比较:
最灵活且常用 Steal + No Force
这一节老师讲的很快,很多地方存在疑惑(为什么要执行到哪个地方,检查点实际的作用)都不太清楚,只能盲目搬运 PPT 了。
判断确定每一个事务是否已完成:
已完成(Redo 关注的):
< START T >\(\cdots\)< COMMIT T >
未完成(Undo 关注的):
检查点类型:
静止检查点:
非静止检查点:
Undo 型日志:
对应 Steal + Force 策略
对于任一事务 T,按下列顺序向磁盘输出 T 的日志信息:
首先,< T, X, \(v\) > 被写到日志中,\(v\) 为 X 的旧值。
其次,OUTPUT(X)
最后,< COMMIT T > 或 < ABORT T >被写到日志中
将事务改变的所有数据写到磁盘前不能提交该事务。
️从日志的尾部开始按日志记录的反序,处理每一日志记录,撤销未完成事务的所有修改,处理到检查点为止。
如果是在 < COMMIT T > 后发生故障,无需对 T 做任何处理,因为 T 已经完成它要做的所有动作了,满足原子性。
反之,T 所有动作带来的影响都要撤销掉,通过 < T, X, \(v\) > 还原。
可能频繁地写磁盘,导致性能下降。
Redo 型日志:
对应 No Steal + No Force 策略
对于任一事务 T,按下列顺序向磁盘输出 T 的日志信息:
首先,< T, X, \(v\) > 被写到日志中,\(v\) 为 X 的新值。
其次,< COMMIT T > 或 < ABORT T >被写到日志中
最后,OUTPUT(X)
与 Undo 写入日志的顺序不一样。
从日志的起始位置(检查点)开始按日志记录的正序处理每一日志记录,重做已提交事务的所有修改。
在检查点处,要将之前所有已提交的事务写回磁盘(Undo 不用是因为它的 OUTPUT 在提交前做完)。
如果是在 < COMMIT T > 前发生故障,无需对 T 做任何处理,因为 T 并没有将影响写入数据库(OUTPUT 还没执行)。
反之,通过 < T, X, \(v\) > 更新数据库。
数据必须在 Commit 后才可见,灵活性差。
Undo/Redo 型日志:
对应 Steal + No Force 策略
对于任一事务 T,按下列顺序向磁盘输出 T 的日志信息:
首先,< T, X, \(u\), \(v\) > 被写到日志中,\(u\) 为 X 的旧值,\(v\) 为 X 的新值。
(< COMMIT T > 或 < ABORT T >)或 OUTPUT(X) 被写到日志中
OUTPUT(X) 或(< COMMIT T > 或 < ABORT T >)被写到日志中
无所谓 OUTPUT 和 COMMIT 谁先写,很灵活。
先自后向前地撤销所有未提交的事务,再自前向后地重做所有已提交的事务。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章