数据库系统原理2022年总结
Table of Contents
关系模型 #
关系的三个基本要素 #
- 基本结构:表
- 基本操作:即关系代数的那些操作,例如并、差、积、选择、投影、交、连接、除
- 完整性约束:实体完整性(对应主码),参照完整性(对应外码),用户自定义完整性
关系的度数和基数 #
- 分别代表属性的个数和元组的个数。即表格的列数和行数。
关系运算的分类 #
关键在于操作的对象不同。关系代数操作集合,关系演算操作元组。但是最终都输出集合。
- 关系代数
- 例如 $\pi_{pname,cname}(\sigma_{cname=c2}(R\bowtie S))$
- 它的操作对象是元组集合(即关系)。无需遍历。只是对元组集合进行高层次的集合运算而已。
- 关系演算
- 元组演算
- 它的操作对象是元组。对所有涉及到的关系里面的**所有元组(即按行)**进行遍历。
- 例如 ${t|(\exists u(R(t)\and W(u)\and t[3] < u[1]))}$
- 域演算
- 它的操作对象是域。对所有涉及到的关系里面的**所有的域(即按列)**进行遍历。
- 和元组演算非常类似。
- 元组演算
- 关系代数与安全的元组关系演算、与安全的域关系演算等价。即他们之间可以互相转换形式。
- 三种运算都是非过程性的,但是其非过程性以域关系演算最好,关系代数最差。因为关系代数几乎都能看出操作顺序了,这就快被踢出非过程性行列了。
- 可见,关系代数是对于关系的一系列操作构成的操作序列,它可以转换成诸如
SQL
的数据库操作语言,其操作对象是集合。但是关系演算是基于逻辑的,直接对于集合的运算,其操作对象**(即谓词逻辑中的谓词变量)是集合中的元组或域**,对应QBE
这类的操作语言。 - 什么是域?
- 即表的一列。
- 所以,表的的一列称为域(字段),表的一行称为元组。因此,关系演算是分别横着算和竖着算。
关系型DB和非关系型DB的区别 #
- 前者的操作对象是元组集合,后者的对象是记录。
- 因此,关系模型的特点是:基于集合的运算。
关系模式和关系的区别与联系 #
- 一个关系模式可以生成很多关系
- 关系模式是恒定的,而关系中的数据可以是时刻变化的
关系和表的区别 #
- 关系必须要满足1NF。
- 关系中不允许出现重复的元组。
主属性 #
- 一个属性,他被包含在任意一个候选码中。
- 因此,区分元组,必然要涉及该属性才能区分。
- 关系的属性中,不是主属性的关系称为非主属性。
外码 #
- 要求外码必须是另一个关系的主码。
约束 #
- 实体完整性约束
- 主码不能为空。
- 参照完整性约束
- 如果R1的t1参照了R2的t2,那么t1的值要么是空值,要么必须能在R2里面找到。
- 这里,t1是外码,t2是主码。
- 用户自定义完整性约束
- 当发生更新操作时,DBMS按照用户自定义的要求来检查是否合法。
- 例如性别只能是男和女。
关系代数 #
整体分类 #
包括集合操作和纯关系操作。
- 集合操作把关系看做元组集合,然后直接进行集合运算。
- 交、差、并、补
- 笛卡尔积
- 纯关系操作与集合没有什么关系,只是为了实现DB而新增的操作。
- 投影(SELECT)、选择(WHERE)
- 连接(JOIN)、更名(AS)
- 除
并 #
- 并相容
- 需要保证参与并的运算双方具有相同的属性数量,且A的第i个属性和B的第i个属性必须是相同类型。
- 也就是说,两个关系的属性必须是一一对应的,才能满足并相容性
- 直观理解:运算双方包含的元素可以随意放到对方的关系下,而不发生问题。
- 并相容是进行集合运算的前提(除笛卡尔积外)。
- 只有满足并相容的关系才能并。因为满足相容性,所以直接粗暴地把双方的元组放到一起,构成新的集合即可。因为是构成集合,所以需要去重。
- 汉语语义:或者…或者…
差 #
- 满足并相容的两个关系才能进行差运算。
- 注意,R-S不等于S-R。因为他们一个留下了R里的元素,一个留下了S里的元素。即不满足交换律。
笛卡尔积 #
笛卡尔积是唯一不需要满足并相容性的集合运算。
- 它对两个集合中的元组分别进行两两拼接。
- 对于度数为n,基数为a的关系R,度数为m,基数为b的关系S,则R和S的笛卡尔积得到了度数为a+b,基数为mn的新的关系。
选择 #
- $\sigma_{con}(R)={t|t\in R \and con(t)=true}$
- 其中,con可以是离散数学里面的逻辑表达式。如果表达元组的某一分量,使用中括号,例如 $t[sno]$。
- 注意运算符优先级:括号,比较运算符,存在任意,NOT,AND,OR
投影 #
- 含义,在R中选出若干列构成一个新的关系
- $\Pi_{col_1,col_2,…}(R)={<t[col_1],t[col_2],…>|t \in R}$
- 其中,col是所选择的列(即属性名),多个之间采用逗号连接。
- 因为关系代数需要输出关系,所以如果投影后出现重复元祖需要去重。
- 选择是对行进行筛选,投影是对列进行筛选。
$\theta$ 连接 #
- 相当于先全连接,然后再选择,即 $R\bowtie_{a\theta b} S=\sigma_{R[a]\theta S[b]}(S\times R).$
- 因为只能使用 $\theta$(该符号在DB中用于代指任意的比较运算符),所以这里的“选择”仅限于元组的属性之间进行比较,并不能使用其他更高级的方式,否则还是自己手写两步吧。
- 注意,虽然解释为先连接后选择,但是实际上DBMS是一气呵成的,所以它比全连接要快不少。
等值连接 #
- 是特殊的 $\theta$连接。即 $R\bowtie_{a= b} S=\sigma_{R[a]= S[b]}(S\times R).$
- 把 $\theta$连接中的 $\theta$换成=。
自然连接 #
- 是特殊的等值连接。即 $R\bowtie S=\sigma_{R[a]= S[a]}(S\times R).$
- 但是注意,自然连接会合并这两个相同的属性,但是等值连接仍然保留。
- 自然连接会消除一切相同的属性名,都只保留一个,所以一定要注意。如果希望能够保留,并且比较两个表之间的情况的,一定要采用等值连接,而非自然连接。例如经典题目1.
- 自动寻找属性名相同的属性,基于他们进行等值连接。
- 如果查询操作涉及多个表,先看能否自然连接,如果不能,则看能否进行等值或theta连接;如果还不能,就利用全连接。
更名 #
- 如果要对同一张表进行连接,一定要使用更名,否则无法区分两者连接的字段原本是属于哪个表的。
- 更名操作是在原地更名。比如我要引入关系R,那么我在写的时候写成 $\rho_{S}(R)$,那么就相当于我这里引入了关系S。这个R在我的表达式中以后以S出现了。
- 常用于“先更名,后连接”。因为更名操作的优先级高于连接。例如 $SC \bowtie_{SC.s=SC1.s} \rho_{SC1}(SC)$.这里就是先把SC更名为SC1,再进行等值连接。在等值连接的条件中,就可以采用新名称去进行描述了。
除 #
- 设R的度是n,S的度是m,对于 $R\div S$ 运算,其结果的特征是:属性由集合R-S的属性构成,其度变成了n-m(这正好和笛卡尔积相反)。
- 因此,如果要进行 $R\div S$ 运算,需要保证S的属性集是R的属性集的真子集,或者说R的度应该严格大于S的度。
- 对于 $R\div S$ ,其结果集合由这样的元组构成:是集合 $\Pi_{R-S}(R)$中的元组,该元组与S中的所有元组进行连接,连接结果都能在R中找到的那些(哪怕是只少了一个,也不会出现在最终的结果中!)。
- 表达“是集合 $\Pi_{R-S}(R)$中的元组,该元组与S中的所有元组进行连接”,也就是进行全连接: $\Pi_{R-S}(R)\times S$
- 表达“连接结果中,在R中不存在的那些”(如果对于 $\Pi_{R-S}(R)$中的元组,它与S中的所有元组进行连接,结果都能在R中找到,那么在这一步它们全都被减去了,否则,会留下来一些。):$\Pi_{R-S}(R)\times S -R$
- 于是,用原来的R减去,就是在R中能找到的那些(注意,在进行集合运算之前,为了保证并相容性,需要多一步投影操作)(也就是把上一步留下来的那些元组的R-S字段投影减去。这就实现了“哪怕是只少了一个,也不会出现在最终的结果中”。):$\Pi_{R-S}(R)-\Pi_{R-S}(\Pi_{R-S}(R)\times S -R)$.
- 应用:查询选修了所有课程的学生的学号。
- $\Pi_{sno,cno}(student_course)\div \Pi_{cno}(course)$.
- 解释:结果的关系,其只有一个属性sno。其中的每个元组和course中的所有的元组连接后的结果,在student_course中必须都存在。也就是说只有该学生选了所有的课程,才能实现在student_course中,这种学生-课程的连接才能够都存在。哪怕是少选了一个也会被减去。
外连接 #
- 产生原因:防止自然连接时,有些元组因为找不到可以连接的元组(因为不存在与他相等的字段),而被扔掉的情况。例如需要统计教师的详细信息,那么需要进行大批的自然连接。例如在连接课程信息时,如果某教师不教课,该教师就会被扔掉。
- 外连接相当于自然连接+失配元组与NIL连接构成的集合。
- 三种外连接
- 左外连接:保持运算符左侧集合的元组不丢失,也就是给其中失配的元组在右边填充NIL。
- 右外连接:保持运算符右侧集合的元组不丢失,也就是给其中失配的元组在左边填充NIL。
- 全外连接:保持参与连接的任何元组都不能丢失。也就是只要失配就补NIL。
- 全外连接=左外连接和右外连接的并集。
- 外连接不执行属性合并操作(虽然是基于自然连接的)。因为结果中并不是该值大家都相等,因为其中有不少是一边NIL,一边有值的。
经典题目 #
-
自己和自己连接
查询学习了001号、002号课程的学生的学号。-
则需要让选课表自己连接自己,且采用等值连接确保选课数据属于同一个学生。于是得到了一张包含“选择课程1”、“选择课程2”的中间结果表格。我们让这个中间结果满足条件即可,也就是分别001、002.
-
$$ \Pi_{SC.s}(\sigma_{SC.c=001\and SC1.c=002}(SC \bowtie_{SC.s=SC1.s} \rho_{SC1}(SC))) $$
-
如果上面换成自然连接就不对。因为两个c属性被合并了。则选择操作结果为空,因为它不可能同时取001、002.
-
-
转化为并相容集合
查询不学习002号课程的学生的学号。-
$$ \Pi_{sno}(S)-\Pi_{sno}(\sigma_{c=002}(S\bowtie SC)) $$
-
为什么需要俩投影相减?因为运算 $S-\sigma_{c=002}(S\bowtie SC)$是错误的。原因是后面两个自然连接得到的集合和S集合是并不相容的,无法进行集合运算。
-
所以要先进行投影,转化为并相容集合,然后才能进行集合运算。
-
关系演算 #
关系演算是对所涉及的关系的各元组进行遍历,以进行判断是否成立,并把成立的那些元组加入到结果构成的集合中。
元组演算标准格式 #
- ${t|t\in R \and con}$.也就是,R中满足con的所有的t构成的集合。
元组演算中的存在量词和全称量词 #
-
他们具有了特殊含义:例如 $\exists (u\in SC \and t[s]=u[s])(u[score]>=60) $,是对SC中的满足t[s]=u[s]的那些元组作为范围,进行一一验证,是否存在使得成立.
-
也就是,它起到了在一定范围内进行遍历并判断的作用。常用于连接操作。例如A连接了B,现在拿着A中的一个元组t,去逐行遍历B中是否存在元组u,满足t和u之间一定的关系。
-
也就是,给定一个范围,通常是给定一个集合,并对其中的范围进行限定。然后,得出该范围后,在该范围内进行一一比较,如果存在一个满足条件,或全都满足条件,则整个表达式为真。
-
例
-
做题技巧:把涉及到的表格都画出来,然后标上指向其中元组的指针。
-
找出计算机系的所有同学?
-
复习一下基本格式:${t|大范围\and条件}$。然后,指针遍历的基本格式是:$\exists 或 \forall (u \in 遍历范围\and [tu的衔接])([tu的衔接]\and 判断t、u关系是否成立的条件)$
-
$$ {t|t\in students\and\exists (u\in dept)(t[dno]=u[dno]\and u[dno]=CS)} $$
- 首先,因为要找的是同学,所以大范围是 $t\in students$。然后在条件中,引入存在量词,作为指针去遍历待连接的表。
- 一个全称或存在量词就相当于引出了一个指针u,该指针遍历其限定范围(这里是 $u\in dept$)的所有元组,并逐一验证其后面跟着的条件(这里是 $t[dno]=u[dno]\and u[dno]=CS$,其中 $t[dno]=u[dno]$起到衔接作用,$u[dno]=CS$用于真正判断。这是一般的书写套路)是否成立。如果成立,则整个表达式成立,t被加入结果集合。
-
找出所有同学的所有课程都及格了的系?
-
$$ {t|t\in dept\and \forall (s\in students\and s[dno]=t[dno])\(\forall (u\in student_course \and s[sno]=u[sno])(u[score]\geq 60))} $$
-
即拿着指针s判断学生表里面所有的的和t是同一人的元组处,拿着u去选课表中比对同一人,它的所有的成绩是否都及格。
-
-
(留做练习)找出学过所有课程的同学。
- $$ {t|t\in students \and \forall (u\in course)(\exists (s\in student_course) \(s[sno]=t[sno]\and u[cno]=s[cno] ))} $$
-
-
注意指针遍历的范围。
- 考虑下面四个公式:
- $\forall(u\in SC \and t[sno]=u[sno])(u[score]>60)\ (1)$
- $\forall(u\in SC )(t[sno]=u[sno]\and u[score]>60)\ (2)$
- $\exists (u\in SC \and t[sno]=u[sno])(u[score]>60)\ (3)$
- $\exists (u\in SC )(t[sno]=u[sno]\and u[score]>60)\ (4)$
- 注意,第一个括号是遍历的范围,第二个括号是需要满足的条件。所以用于衔接的 $t[sno]=u[sno]$ 写在两个括号内是完全不同的含义。
- 对于1、3,其遍历范围是“同一个人”的那些SC内的元组,而2、4是对SC中所有元组遍历。这个范围的区别对存在量词没有影响,但是对全称量词是致命的。因为1和2不相等,但是3和4相等。
- 因为范围扩大,就意味着把那些不满足衔接关系的也纳入了遍历过程。对于要求苛刻的全称量词来说,必然为false(因为只要不是"同一个人",就会给出false。因为判断条件中的 $t[sno]=u[sno]$不成立。)。
- 考虑下面四个公式:
关系代数如何用元组关系演算表达 #
- 选择运算
- $\sigma_{con}(R)={t|t\in R\and con}$
- 投影运算
- $\Pi_A(R)={t[A]|t\in R }$
域演算简介 #
-
元组演算的处理单位是元组,但是域演算的处理单位是域,也就是属性。这些属性是来自于涉及到的各关系的,并且通过形如 $<a,b,c>\in R$的方式来表示他们的来源。一个关系如果度是n,那么在域演算中也必须是有n个域与之对应。例如上述可以看出R的度是3.
-
可以在不同的关系中使用相同的域来代表连接。例如找出乘积不及格的同学的姓名,其中的a、g起到了连接表格的作用。
-
$$ {|\exist a,c,d,e,f,g,l,j,k(<a,b,c,d,e,f>\in student)\and \<g,h,i,j,k>\in course\and <a,g,m>\in student_course\and m<60} $$
-
元组演算和域演算可以互相转化,但是后者的过程性比较差,通常无法实现复杂操作。
关系演算的安全性 #
- 注意,关系代数必然是安全的,因为他在有限的集合上执行有限的运算。
- 定义关系演算的安全性:如果结果不产生无限集合,或者运算过程中不发生无穷验证,那么这个关系演算是安全的。
- 产生无穷集合
- 例如 ${t|t\in student \or 2>1}$,因为条件恒为真。结果所在范围与student无关,而是落在一个不可预知的全集中。
- 出现无穷验证
- 对于全称或存在量词,因为它需要去遍历它要验证的范围,如果这个范围是无穷集合,就会导致无穷验证。
安全元组演算表达式(防止发生无穷验证) #
- 定义DOM(安全约束有限集合),其中包含了元组演算中出现的关系中的属性名。
- 如果要使用全称量词或存在量词,其验证范围不再是给定的范围,而是其和DOM的交集。对于该交集以外的元素,不再进行验证。对于全称量词,默认其为真(因为它遍历只是为了判断是否每个都真);对于存在量词,默认其为假(因为它遍历只是为了判断是否每个都假)。
数据建模与数据库设计 #
三个世界 #
- 客观现实世界、信息世界、计算机世界。例如一头牛,在现实世界里是动物,在信息世界里是汉语“一头牛”,在计算机世界里可以例如编码成
0100 0001
.
数据模型与概念模型 #
- 概念模型是对客观世界的抽象,存在于产品设计中。例如E-R图。
- 数据模型是对概念模型的抽象,存在于计算机软件实现中。例如关系模型、非关系模型。
数据建模和数据库设计 #
- 数据建模是把客观世界抽象成概念模型的过程,而数据库设计是把概念模型实现成数据库的过程。
E-R图基本思想 #
- 即实体-关系模型。它认为客观世界都是由实体和他们之间的联系构成的。因此,可以用这种方法进行数据建模。
实体和实例 #
- 实体并不是“单个实际存在的个体”,而是“这样的存在的个体的集合”。这个集合中的元素称为实例。
- 精确到实体中的某一个,称为实例;如果称某一类实体,则成为实体。这是语义上的区别。
- 可以使用属性来刻画一种实体。而对于每个实例,则是这种属性构成的一个个元组。
属性的分类 #
- 单值、多值。
- 单一、复合。
- 可空、非空(SQL语句
not null
)。 - 原始、导出。
- 例如出生年月是原始属性,年龄是导出属性。
- 满足单值、单一属性,即1NF。
实体之间的联系 #
- 指的是一个实体的实例和另一个实体的实例发生了某种关系。
- 实体和联系都是需要命名的。可以采用一个被命名的表去为双方建立这种联系。这样才能发生“读者”“借阅”“图书”,其中借阅就是联系的命名。
- 换个角度说,如果不命名,就不能区分这两个实体之间的不同联系。例如读者和图书之间除了借阅这种关系,还可以是拥有。
联系的元(度) #
- 这个联系与多少实体有关。(注意是与多少实体有关,而不是其实例)。
- 一元联系
- 例如零件A由零件B和零件C装配而成。
- 其中,零件A、B、C是“零件”实体的三个实例。
- 二元联系
- 例如读者借阅图书
- 三元联系
- 例如供货商、商品、零售商之间的联系
- 因为这个联系涉及了三方。
角色 #
- 考虑零件A由零件B和零件C装配而成。其中,零件A、B、C是“零件”实体的三个实例。
- 如何区分这三个实例在这个联系中发挥的作用?引入角色的概念。即他们三个是不同的角色。
- 当同一实体的不同实例参与联系,为了区分他们的作用是不同的,需要显式地指明其角色是什么。
实体的映射基数 #
- 二元联系的分类
- 一对一联系:例如店长和店面
- 一对多联系:例如画家和它的画
- 多对多联系:例如学生和课程
- 映射基数
- 所谓基数,即一个实体的实例通过联系,能与另一实体中的多少个实例相关联。例如如果是多对一,那么这个实体的一个实例就能与对方实体中的多个实例建立联系。
- 常用映射基数表示,即(1:1)、(1:m)、(n:m)。
- 映射基数是很关键的,涉及到如何进行数据库设计。
- 例如一对一或一对多的情况,可以使用外码依赖实现。
- 但是多对多无法使用外码依赖,否则不满足1NF。此时需要采用额外的联系表,例如我的Nesto中的
GroupPermission
表。
- 实体的映射基数是根据参与联系的双方实体而定的。并不是关系所决定的。
- 例如画家和它的画,画家参与该联系的映射基数是1对多的,而画参与该联系的映射基数是1对1的。
m对n映射的正确理解 #
- 虽然其定义是“m个A实体的实例可以与n个B实体的实例发生关系”,但是实际上对于单个实例,例如A实体的一个实例,它只能完成1对n的映射,而不可能是m对n的。所以m对n的定义是个谬论。其准确的理解应该是对于一方的一个实例,他可以进行1:m的映射;对于另一方的一个实例,其可以进行1:n的映射。
实体参与联系的方式:部分参与和完全参与 #
- 如果该实体中的实例都能参与到联系中,该实体完全参与该联系;如果该实体中存在没有参与到联系中的实例,那么该实体部分参与该联系。
- 在数据库设计时,如果是完全参与,那么其外码属性或额外联系表中它的主码那一项必须是非空属性。否则就可以是允许空属性。
E-R图表达:Chen方法 #
chen方法也就是属性需要写在圈圈里面,然后都连到关系上的那种表达方式。这比较繁琐,并不是工程中所使用的。
-
联系采用菱形框表示。所以很多圆形连接在矩形框上,表示这个矩形框表示的实体由这些圆形表示的属性来刻画;两个矩形连接在一个菱形框上,表示这两个实体参与了这个菱形框表达的联系。
-
多值属性采用双线的圆圈,导出属性采用虚线的圆圈。
-
复合属性采用层次表达,即一个圈可以继续连接其他圈,理论上可以构成一棵树;也可以采用编号的方式表达,不同属性之间编号相同的属于同一个复合属性。
-
实体参与数量和映射基数(箭头表述)
-
联系向其连接的实体分别射出箭头或射线。有箭头的称为“1端”,没箭头的称为“多端”。
-
如果都是箭头,那就是1对1;
-
如果A是多端,B是1端,那么A到B的映射是1对1的,而B到A的映射是1对多的。
-
注意,自己所连接的那根线和自己参与联系的情况没有任何关系。你要看对面那根线。
-
-
如果都没有箭头,就是m对n的。(而且不能准确给出到底是多少对多少)
-
-
如果是单线,就是部分参与;如果是双线,就是完全参与。
- 假设联系到A的是双线,到B的是单线,那么A全部参与了该联系,B部分参与了该联系。
-
-
实体参与数量和映射基数(数字表述)
- 理解方式和箭头法类似。只不过不通过箭头区分,而直接在上面标数字。仍然要注意,自己所连接的那根线和自己参与联系的情况没有任何关系。你要看对面那根线。
- 例如连接实体A的线上写了a,连接实体B的线上写了b,那么A参与联系的映射基数是1对b的,B参与的则是1对a的。
- 此外,可以采用范围表示法,例如A处的线上标称的数字是
1..n
,那么B参与该联系可以是1对1到n之间任何数字的联系,注意最小是1,所以B属于完全参与。如果标称0..n
,那就是说B是部分参与了(因为B中的1个实例可以不与A中的任意实例对应,也就是null
)。 - 参与数量情况和箭头描述一样。都是单线、双线法。因为引入了上述的范围表述,因此可以通过修改最小值,同样能达到参与情况的表述目的。
- 理解方式和箭头法类似。只不过不通过箭头区分,而直接在上面标数字。仍然要注意,自己所连接的那根线和自己参与联系的情况没有任何关系。你要看对面那根线。
E-R图表达:Crow’s Foot方法 #
- 即采用矩形来表示关系,且把属性写在矩形里面。即Nesto采用的方法。
E-R图表达:IDEF1x方法 #
重要概念 #
- 实体被分成了强、弱实体。
- 联系被分成了可标定连接联系和非标定连接联系、分类联系、非确定联系。
强实体和弱实体 #
- 如果该实体能被其主码唯一确定,则这是强实体。
- 如果该实体不能通过其主码而唯一确定,而需要其他实体的码来进行标定。用于标定它的这个实体的码和它本身用于区分的字段共同构成了码。
- 例如:合同、合同条目。合同条目中的用于区分的字段为“合同条目号”,合同中的主码字段为“合同号”,那么在合同条目表中,仅凭借合同条目号不能区分每个条目,因为这个合同条目号是相对于一个合同的,于是会出现很多个“第一条”,很多个“第二条”之类的。此时,如何区分他们?如果我们说“第一个合同的第一条”,“第三个合同的第五条”就能区分了。所以,应当再从合同实体中,把它的码继承过来,于是继承后的合同条目的各属性就变成了“合同号|合同条目号|….”,然后其中的“合同号|合同条目号”两个字段共同构成了合同条目的主码。
- 因此,弱实体不能独立于强实体而存在,否则无法实现上述继承,导致不满足实体完整性约束。
分类联系 #
- 一般实体是对一类分类实体的抽象。它具有比较一般化的特征,而分类实体可以继承一般实体的基本特征,并且添加自己的其他特征。
- 但是注意,分类实体和其一般实体往往具有相同的主码字段。
- 一般实体中有一个“鉴别器属性”,通过它可以确定应该属于哪个分类。从而建立了“分类联系”。
- 属性继承
- 低层实体(即比较具体化的实体)中包含了高层实体(即一般实体)的所有属性。而且共用主码。
- 例如零件表
- 零件是一个一般实体,它具有例如编号(主码)、尺寸、名称等属性
- 外购零件和自制零件是两个继承自它的分类实体,外购的可能有价格,自制的可能有工序。但是他们的主码应该都是编号,这一点和零件(一般实体)相同。
- 泛化和具体化
- 具体化
- 自顶向下。
- 一个一般实体加上不同的属性可以获得不同的分类实体。例如学生+论文=研究生,学生+军训=本科生。
- 泛化
- 自底向上。
- 多种分类实体可以抽象成一个一般实体,多个这样的一般实体相对其更高级的一般实体也能继续抽象。
- 具体化
- 在数据库中的表示
- “ISA”。它表示具体化、泛化(或者说就是一个分类联系)。
概念模型向逻辑模型的转化 #
数据库设计的步骤 #
- 需求分析
- 概念数据库设计——设计出E-R图或IDEF1x图
- 逻辑数据库设计——把E-R图转换为关系模型
- 物理数据库设计——CREATE TABLE…
E-R图到关系的基本转换 #
- 实体转换为关系
- 属性转换为关系的属性
- 如何对特殊属性进行转换
- 复合属性
- 取代法:把复合属性用其包含的各个字段原地取代
- 摆烂法:或把复合属性当成简单属性直接转换
- 多值属性
- 将主码和该多值属性单独拿出来作为一个关系。
- 例如Nesto中的
GroupPermission
。
- 复合属性
- 如何对特殊联系进行转换
- 非全参与的一对一联系(0..1联系)
- 将该联系定义为一个关系。属性采用各自的主码进行拼凑。
- 例如配偶关系:一个职工实体上可以有一个配偶关系,由于是配偶所以是一对一联系,由于有人单身所以是非全参与。则单独把“配偶关系”拿出来作为一个关系,其属性是丈夫的ID、妻子的ID(可见由双方主码构成)。
- 有一方全部参与的一对一联系(一边1..1,另一边0..1)
- 将部分参与方关键字纳入到全参与方属性
- 注意:怎么看是全参与方还是非全参与方?假设全参与方出现了不参与的情况。看是否符合常理。
- 例如员工和部门的关系:员工不一定是经理的身份,但是部门必须要有经理在管理,且一山不容二虎。所以员工部分参与管理了部门且是一对一的,而部门全部参与且一对一地被管理。因此只需在全部参与一方(即部门表)中把员工的ID纳入到属性中,表示这个部门由谁在管理。
- 将部分参与方关键字纳入到全参与方属性
- 一对多联系
- 将单方的关键字纳入多方的属性
- 注意:怎么看是单方还是多方?假设一方只有一个实例,看会有什么情况出现。那根据“一对多”的定义,其对方必然出现很多实例,很多实例那个就是多方。
- 例如班主任和学生的关系,老师可以是多个学生的班主任,但是一个学生只能由一个老师担任班主任。此时在多方(学生方)纳入一个教师ID。
- 将单方的关键字纳入多方的属性
- 多对多联系
- 双方主码单独拿出来作为一个关系。
- 例如Nesto中的
GroupPermission
。
- 多元联系
- 例如三个之间的联系:供货商、零件、工程项目。
- 法1:必须非空
- 转换为supply(工程项目,供货商,零件,联系集上的属性例如数量)
- 因为三者共同构成主码,所以都必须全参与。不能空。
- 法2:可以空
- 转换为supply(条目号,工程项目,供货商,零件,联系集上的属性例如数量)
- 好处:因为现在条目号做主码了,所以现在三者可以随意摆烂了。
- 法3:转换为 $C^2_3$ =3个2元关系分别表示。
- 法1:必须非空
- 例如三个之间的联系:供货商、零件、工程项目。
- 非全参与的一对一联系(0..1联系)
- 如何对特殊实体进行转换
- 弱实体
- 把弱实体的各属性连同其依赖的实体的主码一起构成一个关系
- 也就是模仿了把依赖的实体的主码继承下来的过程
- 泛化实体和具体化实体
- 一般情况下,具体化实体只需要建立其新增的属性,然后纳入对应泛化实体的主码。(这是根据分类关系的定义。)
- 例如学生具体化成了本科生,那么可以建立学生(sid,name),本科生(sid,军训),研究生(sid,论文)。可见,在具体化实体中,不再需要包含高层实体的除主码外的其他属性了。
- 如果某泛化实体的各具体化实体包含了其全部,也就是例如上面的例子,一个学校的学生全都是本科生或研究生,不包括既不是本科生又不是研究生的无业游民,那么就可以不建立学生表(即那个泛化实体),而只去建立本科生(sid,name,军训),研究生(sid,name,论文)。也就是把高层实体的所有属性都纳入了。
- 好处:查询具体实体不需要查询俩表了(先具体实体表,然后泛化实体表)。坏处:有冗余。
- 一般情况下,具体化实体只需要建立其新增的属性,然后纳入对应泛化实体的主码。(这是根据分类关系的定义。)
- 弱实体
转换过程中出现的问题 #
- 不使用外码而导致的问题
- 受控冗余
- 例如把教师的详细信息直接存到每个学生身上,那么教师就算变更一点信息也需要遍历每个学生。
- 插入异常
- 例如新学生入学,还没有分班,故班级为null,则插不进去。
- 删除异常
- 例如一批学生毕业,如果某专业的学生全都毕业了,那这个系就消失了。
- 受控冗余
什么是数据库设计理论 #
- 数据库设计理论由数据依赖理论、关系范式理论、模式分解理论构成。
- 数据依赖理论包括函数依赖、多值依赖。
- 关系范式理论包括1NF、2NF、3NF、BCNF、4NF。
- 模式分解理论包括无损连接分解、保持依赖分解。
数据依赖理论 #
函数依赖 #
- 定义
- 对于属性集合 $U={A_1,A_2,…,A_n}$,构成了一个关系模式 $R(U)$ 。
- 且,对于关系模式U,有 $X\sub U,Y\sub U$。
- 则对于 $R(U)$ 的任意一个可能的关系r,r中不可能存在两个元组,使得他们落在X中的属性的值相等,而落在Y中的属性的值不同。
- 则称X函数决定Y。X->Y。
- 示例
- U={sno,sname,sage,class,monitor,cno,score},则{sno}函数决定了{sname,sage}。其中,X={sno},Y={sname,sage}。
- 理解
- 函数依赖是同一关系的属性值之间的取值约束关系。
- 特殊的函数依赖
- 平凡的函数依赖
- 若 $Y\sub X$,则是平凡的函数依赖。因为这是必然成立的(全集推子集)。
- 恒成立的函数依赖
- 如果在r中找不出落在X中的属性值相同的两个元组,则对于任意的Y,函数依赖X->Y恒成立。
- 完全函数依赖和部分函数依赖
- 如果任意 $X'\sub X$,X’都不能决定Y,则X->Y是完全函数依赖。也就是说X是可以函数确定Y的最小集合。这类似于候选码和超码的关系:完全函数依赖相当于候选码,部分函数依赖相当于超码。
- 如果存在部分函数依赖,则说明存在冗余。
- 传递函数依赖
- 若X->Y,Y->Z成立,且他们都不平凡,且Y不能决定X,且Z不能包含于X,则这是一个传递函数依赖。(即X->Y不平凡,Y->Z不平凡,X->Z不平凡。)
- 如果存在传递函数依赖,则说明存在冗余。
- 平凡的函数依赖
码 #
- 候选码和超码
- 对于 $K\sub U$,如果K能够完全函数决定U,则称K是候选码。
- 主码可以在任意一种这样的候选码中产生。
- 非主属性是不包含在任何候选码中的属性。
- 若此时还有 $S\sub K$,则S是超码。
- 外码
- 如果有属性集合 $U_1,U_2$,有属性集合 $X\sub U_1$,但是X不是$R(U_1)$的候选码,却是 $R(U_2)$的候选码,则X是外码。
逻辑蕴含 #
- 是隐含的函数依赖。根据一些现有的函数依赖,能推出来新的这个函数依赖,则称现有的函数依赖集逻辑蕴含了这个新的函数依赖。
- 设关系模式R(U)有函数依赖集F,如果从F所包含的函数依赖中能推出函数依赖 X->Y,则称F逻辑蕴含X->Y。
函数依赖集闭包 #
- 现有函数依赖集F,该F逻辑蕴含的所有函数依赖构成了F的闭包,记作 $F^+$。
- 因此,即便是小小的F,也可以有大大的能量。只要它蕴含得够有深度,$F^+$也可以很大。如果一个 $F=F^+$,那么称这个F是完备的函数依赖集。
推导逻辑蕴含
的利器:函数依赖的Armstrong公理 #
- 前提:已知关系模式 $R(U,F)$。其中U是关系模式的属性集,F是关系模式的函数依赖集。
- A1:自反律
- $Y\sub X\sub U$,则X->Y $\in F^+$.
- 属性集能函数决定它的子集。
- A2:增广律
- 则X->Y $\in F^+$,则XZ->YZ $\in F^+$.
- 两边同乘,仍然成立。
- A3:传递律
- X->Y $\in F^+$,Y->Z $\in F^+$则X->Z $\in F^+$.
- 推论:被决定方的分解与合并
- B1:合并律:合并两个被决定方
- X->Y $\in F^+$,X->Z $\in F^+$,则X->YZ $\in F^+$.
- B3:分解律:把两个被决定方分解开
- $Z\sub Y$,X->Y $\in F^+$,则X->Z $\in F^+$.
- B1:合并律:合并两个被决定方
- 推论:条件扩大
- B2:伪传递律
- X->Y $\in F^+$,WY->Z $\in F^+$,则WX->Z $\in F^+$.
- B2:伪传递律
Armstrong公理是完备的 #
- $F^+$逻辑蕴含的任何函数依赖都能通过A1、A2、A3在F上推出。
属性集闭包 #
- 现有函数依赖集F,属性集U,他们构成关系模式 $R(U,F)$,现在有一属性集 $X\sub U$,定义 $X^+_F$为X在函数依赖集F的逻辑蕴含下,能够函数决定的所有属性构成的集合。即 $X^+_F={A|X\rightarrow A\in F^+}$.
- 注意理解
- 属性集闭包是属性的集合,而不是函数依赖的集合。因此上面给出的定义中,集合的描述元素是属性A。(Attribute.)
- 求属性集闭包的算法
- 输入:属性集X,函数依赖集F;输出: $X^+_F$.
- 初始化结果集合为给定的X集合。
- loop:对于结果集合的每个属性子集,如果它能通过给定的F通过Armstrong推出能够函数决定的新的属性,就把这个新属性加入结果集合。
- 如果结果集合不再发生改变,则算法停止,否则jmp loop。
属性集闭包引理 #
- $X\rightarrow Y\in F^+ \iff Y\in X^+_F$.(注意是充要条件。)
- 因此,可以通过求属性集闭包来判断函数依赖 X->Y是否 $\in F^+$。这样就不用求F+了,因为几乎没法求。
函数依赖集的覆盖关系 #
-
概念
-
现有属性集U,函数依赖集F、G,如果对于 $R(U)$,有 $F^+=U^+$,则F和U互相覆盖。如果他们互相覆盖,那么他们等价。
-
覆盖是基于闭包的。也就是说,如果G的闭包 $G^+$ 能够包含了F的闭包 $F^+$ ,那么F被G覆盖。
-
-
理解
-
函数依赖集的闭包才是这个函数依赖集的本体啊!因为等价是根据闭包来判断的,而不是函数依赖集本身!也就是说,就算函数依赖集本身发生变化,如果其闭包不变,则仍然等价,相当于没有发生变化。
-
数学中,等号是最重要的,它决定了万物法则。所以我们只能通过函数依赖集闭包来判断两个函数依赖集的关系,而不能通过其本身来判断。
-
函数依赖集的最小覆盖 #
-
函数依赖集的最小覆盖是一个函数依赖集。它是最小的能覆盖住现有函数依赖集的函数依赖集。
要注意区分最小覆盖和覆盖:覆盖只是两个函数依赖集之间的一个关系,而最小覆盖是一个函数依赖集。即一个是关系,一个是集合。
-
前提:一个引理
- 引理:每个函数依赖集F都能被一个里面都是形如 $X\rightarrow A$(即右边都只有一个属性)(注意,A是Attribute,只是一个属性,但是X可以是集合)的函数依赖集G所覆盖。也就是说,任意的函数依赖集都等价于一个上述这种最简形式的函数依赖集。
-
概念
- 现有函数依赖集F,如果F同时满足下列三个条件,则F是一个最小覆盖(又称最小依赖集)
后件是单属性
F中的函数依赖都是形如 $X\rightarrow A$的(即右边都只有一个属性)(注意,A是Attribute,只是一个属性,但是X可以是集合)。没有多余函数依赖
F中不存在多余的函数依赖,凡是只要去掉一个,$F^+$就变了(该函数依赖集和原来不再等价了)。没有多余属性
F中不存在多余的属性,凡是只要去掉一个(当然同时也要去掉其关联的函数依赖),$F^+$就变了(该函数依赖集和原来不再等价了)。
- 也就是说,最小覆盖是最小的那个能保持 $F^+$的性质的那个函数依赖集。对于同一个 $F^+$,它可以有很多种等价的F表示,但是最小覆盖是“最小”的那个。上面三条同时满足能保证他“最小”。
- 现有函数依赖集F,如果F同时满足下列三个条件,则F是一个最小覆盖(又称最小依赖集)
-
定理
- 每个函数依赖集都有其最小覆盖。
-
求解算法
- 输入:函数依赖集F;输出:函数依赖集最小覆盖F'。
- 首先根据引理,找出与F等价的那个包含函数依赖都是形如 $X\rightarrow A$的(即右边都只有一个属性)的函数依赖集作为结果集合。
- 考虑结果集合的每一个依赖 $X\rightarrow Y$,如果删去仍然能保持和原来等价,则删去;
- 考虑结果集合的每一个属性Z,如果把他删除(连通它所在的函数依赖中),如果删去仍然能保持和原来等价,则删去;
- 于是,得到了和原来F等价的结果集合,输出。
关系范式理论 #
1NF #
- 不允许复合属性、多值属性这两种特殊属性的,即为满足1NF。
- 例如下面的情况:
| person |
|phone|passwd|
|12..3|asdaw |
...
|72..1|mdd2w |
- 可见,person下又分出来两个字段,则person这个表头相当于做了合并单元格操作。
- 或者说,1NF不允许关系进行嵌套。因此字段不可再分。
转换为1NF #
- 消除多值属性、复合属性。从而都转换为简单属性。
- 参考“概念模型转换为关系模型”。
2NF #
- 对于依赖于候选键的非主属性,如果该非主属性对候选码没有不完全依赖的,即为满足2NF。
- 可以消除数据冗余。
- 例如候选码是{sno,cno},非主属性有cn、sd。有 sno->sd $\in F$, sno->cn $\in F$,那么{sno,cno}->sd、{sno,cno}->cn,不满足2NF。
判断是否为2NF #
- 找出候选码、非主属性,并找出候选码参与的部分依赖。
- 例如学生(学号,姓名,班级,课程号,课程名,成绩,教师,教师职务)
- 找出候选码:{学号,课程号}
- 因为这里面有学生信息,还有课程信息,所以候选码由两个ID构成!
- 找出非主属性:姓名、课程名
- 找出候选码参与的部分依赖:{学号、课程号}->姓名
- 找出候选码:{学号,课程号}
分解为2NF #
- 拆成2个关系。其中一个旨在把候选码拆成更小的候选码,同时把非主属性包含进来,从而让拆分后的小候选码能够完全决定非主属性;另一个关系保留原来的候选码的情况,其它剩余的属性包含进去。
- 例如
- 上例中可以把sno、sd和cn拆到一个关系中,这样sno在这个关系中是候选码,而且能完全决定sd和cn这两个非主属性了。
- 剩下的关系中仍然保留sno和cno,因为他们还得做候选码呢。
- 实际上可以随便拆。只要保证不改变意思,且满足2nf即可。
3NF #
- 对于依赖于候选键的非主属性,如果该非主属性对候选码没有传递依赖的,即为满足3NF。
- 可以消除数据冗余。
- 例如候选码是{sid,pid},非主属性是mgr,现在有{sid,pid}->did,{sid,did}->mgr,则mgr通过传递依赖来依赖于候选码。
- 和2NF的关系:如果满足了3NF,则必然满足2NF。
分解为3NF #
- 为F中的每个函数依赖都建立一个关系模式。这样就不可能出现传递了。
- 如果过多,在满足3NF的情况下可以合并一下。
BCNF #
- 所有非主属性都得依赖于候选码,即为满足BCNF。
- 例如候选码是{城市,街道},现在有 {城市,街道}->邮政编码,邮政编码->城市,则存在“邮政编码->城市”导致不满足BCNF。但是它满足3NF,因为这并不是一个传递依赖(因为A->B,B->C,而A->C是平凡的,不算作C传递依赖A。)。
分解为BCNF #
- 给定函数依赖集F,把其中左边不是候选码的函数依赖都单独拿出来分别作为关系模式,剩余的自己组成一个关系模式。
- 如果有必要,可以合并。
- 例如U={A,B,C,D,E,F,G},给定F={A->B,A->C,C->D,C->E,E->FG},可以确定候选码A,则A->B,A->C是符合要求的,但是C->D,C->E,E->FG不符合要求,所以分解成如下几个关系:{R1(C,D),R2(C,E),R3(E,F,G),R4(A,B,C)}。这样一来,R1的候选码变成了C,从而满足了BCNF,其他类似。对于R4,其天生满足BCNF。此外,可以合并R1、R2.
范式 | 解决问题 | 优化 | 关系 |
---|---|---|---|
1NF | 属性值不为简单属性 | 规范属性类型 | - |
2NF | 对于那些依赖于候选码的非主属性,存在不完全依赖于候选码的情况 | 消除冗余 | - |
3NF | 对于那些依赖于候选码的非主属性,存在传递依赖于候选码的情况 | 消除冗余 | 满足3NF则必然满足2NF |
BCNF | 存在非主属性不依赖于候选码 | 满足BCNF则必然满足3NF;不满足3NF必然不满足BCNF | |
4NF | 存在非主属性不多值依赖于候选码 |
模式分解理论 #
存储 #
磁盘读写时间的计算 #
- 先寻道:磁头移动到正确的磁道上方。
- 再旋转:磁头到了正确的磁道上,这个时候旋转才有用
- 最后传输。
提高访问速度的方式 #
- 降低IO次数
- 降低排队等待时间
- 降低寻道时间
- 磁盘块连续存储
- 多柱面并行存储(因为一次读取能把各盘面上的当前扇区都拿到)(对于老式磁盘,是这种情况)
- RAID多体交叉思想
RAID #
- 多体交叉拆分算法
- 不同的拆分算法可以实现不同的并行方式。
- 比特级拆分:对字节进行拆分,把一个字节拆成8个bit,每个bit分别存在不同的并行磁盘上
- 块级拆分:对文件进行拆分,把文件的不同块存在不同的并行磁盘上。
- RAID0
- 没有冗余,只实现并行存取
- RAID1
- 完全冗余,直接镜像一份
- 例如原来需要4块磁盘,那我再加4块,把内容复制一遍
- RAID2
- 基于比特级拆分。
- 采用4-3码(海明码)校验。因此,磁盘的分配比例是4数据:3校验。
- RAID3
- 采用Bit-interleaving(数据交错存储)技术,它需要通过编码再将数据比特分割后分别存在硬盘中(Wikipedia)
- RAID4
- 基于块级拆分。其他同RAID3.
- RAID5
- 基于块级拆分。
- 在RAID4的基础上,使5个盘互位校验盘,解决了RAID4中,校验盘成为性能瓶颈的问题(且磨损严重)。
DBMS索引和Inode表的关系 #
-
DBMS可以利用OS的FAT表先占用一些磁盘块。然后,就可以使用自己的索引去更加高效地管理这些磁盘快了。
-
索引能给出表项具体在哪个磁盘块上。
区别:OS给出的是文件在哪个块上,DBMS给出的是表在哪个块上。
三级结构,两层交互 #
- 磁盘
- 存储数据库的真正数据
- 内存(缓冲区)
- 存储当前需要进行交互的数据。必须把磁盘中的块读入内存(我们认为磁盘快和内存页是一样大的),CPU才能处理。
- 负责协调内存空间和磁盘之间关系的,是缓冲区。
- 缓冲区掌握了内存页和磁盘块的映射索引,实现与磁盘的交互(读入写回)。
- 缓冲区还掌握了记录和其所在页的映射索引,实现与DBMS的交互(数据缓冲)。
- DBMS
- DBMS掌握了一套索引。内容是哪个记录在哪个磁盘块上(实现宏观上的与磁盘的交互。但是,实际上中间通过缓冲区实现具体IO细节,但是对DBMS来说,它可以实现直接根据自己的索引表去访问磁盘块上的记录)。
- DBMS和磁盘通过缓冲区交互的过程
- DBMS需要访问一个块,缓冲区读入这个块到内存,建立记录和内存中指针(页)的索引(这是给DBMS看的),建立该页和磁盘快的索引(这是给磁盘看的),牵线搭桥,完成了任务。
数据存储与查询实现 #
- 自底向上,DBMS的存储查询实现是:
- 存储管理器
- 处于系统最底层,负责控制磁盘读写,对上层提供块读写接口
- 缓冲区管理器
- 负责管理内存缓冲区,控制内存分配
- 实现DBMS和外部存储器之间的通信
- 索引文件记录管理器
- 负责管理DB的三块信息:操作计划、数据模式、数据控制信息。
- 这三块信息分别由DML引擎、DDL引擎、DCL引擎操作和控制。
- 三个引擎
- DML引擎、DDL引擎、DCL引擎分别接受经DML编译器、DDL编译器、DCL编译器编译得到的执行序列,进行实际操作。
- 各种操作的实际实现是经过了“三级结构,两层交互”。
- 三个编译器
- DML编译器、DDL编译器、DCL编译器负责接受用户输入的DML、DDL、DCL命令并编译,供引擎来执行。
- 特别地,DML编译器会把算法库中的各种算法进行选择性组合,给出调用顺序,生成查询计划。该查询计划可以传递给DML引擎。DML引擎执行计划的时候,还会再去调用算法库,实现查询操作。
记录的存储 #
- 定长和变长
- 定长存储
- 可以直接根据长度偏移量得出这是哪条记录
- 变长存储
- 按指针定位
- 按定界符(开始结束标志)定位
- 定长存储
- 跨块和非跨块
- 跨块存储
- 如果这个块剩下的空间不够存一条记录,那就能存多长存多长,然后从下一块开始处继续存。中间用指针链接。
- 问题:块之间有关联,对这些块难以并行操作
- 非跨块存储
- 空着。
- 跨块存储
- 块分配方式
- 连续分配
- 存在扩展问题,但是速度快
- 链接分配
- 速度慢
- 按Segment分配
- Segment是若干连续磁盘块。不同Segments之间可以用指针链接
- 这是前两种方法的折中,兼顾了二者优点
- 索引分配
- 类似FAT
- 连续分配
文件组织 #
- 无序记录文件(堆文件)
- 每次来了新记录都加在尾部
- 删除记录只需要标记一下,然后来新记录可以优先从这里面找(但是可能因为前后记录大小不同造成内碎片)
- 数据库重组:压缩内碎片,提升效率
- 有序记录文件(排序文件)
- 查找效率高,可以折半搜索
- 用于排序的字段称为排序字段、排序码
- 插入慢
- 可以引入“overflow表”来给插入提速,overflow表类似于一个堆文件。可以预见,当overflow表很大时,有序记录文件的查询效率向堆文件靠拢。
- 数据库重组:把overflow表并入排序文件。
- 散列文件
- 一般情况下一个桶对应一个块。通过表的某字段计算其散列,得到对应的桶。用于计算的这个字段和上面一种方法类似,称为散列字段、散列码。
- 如果发生哈希碰撞,桶就对应不止一个块了。这个时候每个桶可以看做一个小的堆文件。
- 桶是有大小的。如果碰撞过于离谱,可以使用overflow桶。即:在桶的尾部加一个指针,连接到新桶,从而扩容。通过这种方式,可以无限扩容一个桶。
- 聚簇文件
- 把某一字段的值相同或相似的记录尽量放在连续的块。
- 如果几个表相互关联,把他们按“相似”处理,可以对多表查询的情况进行提速。
Oracle数据库结构 #
- 逻辑存储层
- 表空间:是整个数据库的顶级结构。数据库本身有一个系统表空间(SYSTEM),存储数据库各项信息。用户可以创建表空间,并自己命名。
- 表空间即表的存放空间。一个表空间可以有多个表。
- 操作系统文件:一个表空间可以由一个或多个操作系统文件构成。但是一个操作系统文件只能与一个数据库相连,显然这是一个单向的一对多的关系。
- 因为多个文件构成一个表空间,所以操作往往是跨文件的。
- 操作系统文件的作用只是用于占位,以保证OS和DBMS的一致性。这个可以类比Windows下的Pagefile。文件把这块空间划定了,这块空间就归DBMS管了。DBMS直接从OS处接管这块空间的存取控制。
- 表和文件之间没有对应关系。一个表可以存放在多个文件中。(文件只是起到占位作用。实际怎么存还是看在哪些块上。)
- 表空间:是整个数据库的顶级结构。数据库本身有一个系统表空间(SYSTEM),存储数据库各项信息。用户可以创建表空间,并自己命名。
- 物理存储层
- 物理存储层是逻辑存储层的直接下层。为其提供存储服务。
- 段:分配了一段特定数据结构的盘区。
- 分为数据段、索引段、临时段
- 段和表之间没有数量上的对应关系。可以是任意对任意的。
- 盘区:特定数量的连续数据块。该“特定数量”可以需求视情况由DBMS管理员动态调整。
- 按盘区访问数据库,盘区大小很关键。如果数据库很大,显然大盘区访问会更容易找到目标。
- 数据块
- 是最小的IO单位。
事务概述 #
事务的定义 #
- 数据库提供的控制数据操作的手段。通过该手段,程序可以把一系列一系列操作作为一个整体进行操作和控制,并能够保证一致性的状态转换。
- 从程序员的角度,一系列SQL语句的整体执行就可以看作一个事务。
- 由程序员提出,并开始执行;在结束时,可以提交
commit
或撤销rollback
。
- 由程序员提出,并开始执行;在结束时,可以提交
- 事务以开始执行SQL语句为开始标志,以
commit
或rollback
为结束标志,不一定非要有begin transaction
或end transaction
。- 有多少次
commit
或rollback
,就产生多少个事务。
- 有多少次
- 具有ACID特性的操作称为事务。
为什么需要事务的并发控制 #
- 多个事务可以并发执行。如果不加以管理,会导致数据的不一致性。
- 在事务并发时,通过对SQL语句操作的顺序进行合理安排,来保证事务宏观的独立性、完整性、正确性,就是事务的并发控制。
- 三种典型的不一致错误:损失修改、不能重复读、脏读
事务的特性:ACID #
Atomicity
原子性- 事务包含的操作要么都不做,要么一次性都做完。
Consistency
一致性- 保证在事务并发过程中,仍保证数据的一致性(即不能出现上述三种不一致性错误)
Isolation
隔离性- 事务在并发的过程中仍然要保持独立。互相不能影响结果。
- 例如就算是T1、T2两个事务并发执行、其结果应当等效于先T1、后T2,或先T2、后T1.
Durability
持久性- 持久性包含两个方面:
- 保证提交是持久的。已提交的数据应当写在了磁盘上,不应丢失。
- 被撤销事务的影响是可恢复的。
重新定义事务 #
具有ACID特性的操作称为事务。
事务管理器 #
- 当应用程序进行SQL操作,事务管理器负责
产生
一个新的事务,并打时间戳
。 - 事务的后续的
操作
由事务管理器产生、管理。
事务调度器 #
- 对当前存在的所有事务的操作产生一个合理的操作序列。
- 目标:保证一致性。
事务调度与可串行性 #
事务调度 #
事务的基本步
即事务的基本操作,包括读、写、控制操作(例如加锁、解锁)- 事务的基本步可以类比OS中进程调度的机器指令。
事务调度
即事务的各基本步的一个合理的执行顺序。
串行调度和并发调度 #
- 串行调度:例如当前有T1、T2都要执行,那我真的就是让T1先跑,跑完了才轮到T2.即:先依次执行T1的基本步,然后是T2的。
- 并发调度:例如T1的基本步和T2的基本步是交错执行的。就算只有一个基本步交错执行了,我们也称为是并发调度。
- 当且仅当T1、T2两个事务并发调度,其结果应当等效于先T1、后T2,或先T2、后T1,才说明这个并发调度是
正确的
。
- 当且仅当T1、T2两个事务并发调度,其结果应当等效于先T1、后T2,或先T2、后T1,才说明这个并发调度是
可串行性(可串行化) #
- 可串行性是用来
衡量
并发调度正确性
的一个概念。即:不管当前数据库状态如何,这个调度对数据库的影响和某个串行调度相同
,则说明这个调度是可串行化的,或具有可串行性。 - 当T1、T2两个事务并发调度,其结果确实等效于先T1、后T2,或先T2、后T1,那就是说这是个可串行化的并发调度了。
充分不必要 #
可串行化调度一定是正确的并行调度,但是正确的并行调度不一定可串行化。
可串行化调度是并行调度的子集
。
等效串行序列 #
- 可串行化的并发调度的等效串行序列
不唯一
。 - 例如上面说的既可以等效为T1再T2,也可以是T2再T1.
表达模型 #
$r_T(A)$表示事务T读A元素。 $w_T(A)$表示事务T写A元素。这两个操作属于基本操作(基本步)。
冲突 #
- 冲突的定义
- 调度中的一对连续的动作,如果他们俩
调换顺序
,那么所涉及的事务中至少一个的行为发生改变
。
- 调度中的一对连续的动作,如果他们俩
- 如果两个操作有
冲突
,那么他们不能交换次序
。否则,可以交换。 - 常见冲突
同一事务
内相邻的两个基本步肯定冲突。否则就改变了编写这个事务的本意。不同事务
对同一元素的紧挨着的写
操作是冲突的。不同事务
对同一元素的紧挨着的一读一写
操作是冲突的。
冲突可串行性(冲突可串行化) #
- 如果一个调度,通过交换两个
相邻的
无冲突操作可以转换到(注意不是等效,而是真的转换得到)某串行调度,那么这个调度是冲突可串行化的。- 一般需要一系列交换才能达到目标。
冲突可串行性调度是可串行性调度的子集。
- 冲突可串行性的作用:因为不能直接判断并发调度的正确性,不能直接判断可串行性调度,但是我们可以判断是否具有冲突可串行性。如果具有冲突可串行性,由于包含关系,则我们就判断出了它具有可串行性,也就是说该并发调度是正确的。
因此,冲突可串行性调度是判断某并发调度是否正确的方法。
- 判断是否是冲突可串行化的一般方法:例如基本步序列 $r_2(A);r_1(B);w_2(A);r_2(B);r_3(A);w_1(B);w_3(A);w_2(B)$ ,这里有三个事务 $T_1,T_2,T_3$ ,可以画一个图,其中节点有1、2、3号分别代表这三个事务。观察基本步序列,可以看到$w_2(A);r_3(A)$、$r_2(B);w_1(B)$、$w_1(B);w_2(B)$ 是三对冲突基本步,他们不能交换顺序,也就是说必须保证他们的顺序,也就是说他们直接确定了这三个事务的相对顺序必须满足的条件。根据此,画前驱图:如果事务 $T_1$ 必须在 $T_2$ 之前完成,那么就有一条从1号点指向2号点的边。由此画图,如果最终的图没有环,这就是一个冲突可串行化的调度,且最终的串行化调度顺序就是这个图的
拓扑序列
。否则如果有环,这就不是一个冲突可串行化调度。可以看出这里在1、2点之间有环,所以~。
基于锁的并发控制 #
并发控制的基本方法(如何产生一个冲突可串行化调度?) #
- 基于封锁的并发控制(本节)
- 基于时间戳的并发控制
- 基于有效性确认的并发控制
- 基于封锁的控制是
基于锁的
方法;基于时间戳、有效性的属于基于撤回的
方法。
锁 #
- 锁是控制并发的手段
- 每一个数据元素都有自己的锁
- 每一事务读写数据元素前,要获得
该元素持有的锁
;读写完毕后,要释放
锁- 如果其他事务已经获得该锁,那么当前事务需要
进入等待
- 如果其他事务已经获得该锁,那么当前事务需要
元素的锁可以理解为该元素有一个初始值是1的信号量。事务相当于是进程。加锁是P,解锁是V。因为信号量初始值是1,所以如果该锁已经被其他事务拿走,新来的事务进入等待。
操作表达模型 #
$L_i(A)$表示事务i对元素A加锁; $U_i(A)$表示事务i对元素A解锁。这两个操作和前面提到的读、写一样都属于基本操作(基本步)。
锁表 #
- 锁表中存储了当前各锁的情况。
- 字段一般是
元素-事务-类型
。- 表示哪个事务对哪个元素加了锁;或是哪个事务对哪个元素加了什么类型的锁。