【个人笔记】数据库原理(西电)

发布于:2025-06-06 ⋅ 阅读:(22) ⋅ 点赞:(0)

第一章

ER图和关系分解见课本p69

ER图是常用的 概念模型 

  • 方形:实体
  • 圆形:属性
  • 菱形:关系 

常用的逻辑模型

说白了:增删改查 

 几种数据模型的基本概念

层次模型:树状结构【只能处理一对多的关系,只有沿着从根节点到子节点的方向看才有意义】

【找到逐级的包含关系,构建树】 

网状模型:每个实体分别建表,然后实体间联系再建一个表【维护代价大】

关系模型p45

 关系,就是元组的集合;元组的核心,在于主码。

模式p53

数据库模式体系:三层模式,两层映射 

  • 外模式【多个】:【局部数据的逻辑结构和联系】一对多,一个应用只能访问一个外模式对应数据,但一个用户可以有多个应用,因此可以访问多个外模式,用户只能看见和访问对应外模式的数据
  • 模式【一个】:【全部数据的逻辑结构和联系,和完整性、规范性约束】
  • 内模式【一个】:【数据的物理结构和存储方式】

第二章ppt1

n元关系只要n个域就行,不要求是不同的域 

列是同质的: 一个列上的所有元素的类型是一样的

原子值:比如“姓名” 属性的值就是一个原子值,像 “张三”,它不能再分割成更小的有意义的数据部分 。

:同质/来源/ 行序/列序 /元组唯一/分量原子值

参照关系

可以自己和自己参照!比如员工和其上级

两个完整性约束 

下图最后一句是说:如果R的外码包含多个属性,那么他们要么全都是空的,要么就是要和S中的某个元组对应

第二章ppt2

 四大范式NF

函数依赖【课本p76】

✅函数依赖x->y可以有,对于相同的y,有不同的x

 易错例!

平凡& 非平凡函数依赖

完全依赖F,部分依赖P& 传递依赖T【注意有“不包含,不反推”的要求】【注意各自箭头上的字母】

上面的这些叫函数依赖

多值依赖p30开始【不考】

第五章:索引

蓝块中的感觉比较杂,不确定考不考

随机访问的意义:适用于非连续的存储:不同记录可能分布在不同磁盘块。随机访问允许直接定位并读取这些分散的数据,无需按顺序遍历大量无关数据。在特定场合下,随机访问有不可被替代的作用

文件内的三种组织类型

  • OLAP:适合列存;
  • OLTP:适合行存
  • HTAP(散列):不同hash桶之间无序,hash桶内部有序

多表聚簇类似于将多个表连接后再存储,方便多表查询,适用于需要同时对多个表的查询

两种堆文件存储方式【page之间的存储】:链表堆和目录堆(文件内部的页面之间的存储)

 

每个页page里面的存储:

定长元组:顺序存储

变长元组的存储:分槽slot页面结构

  • Entries:实体数量
  • 空闲空间末尾指针:指向空闲空间
  • slot中的元组:存储指针和大小

下图:上边是元组的内容,下面是对应的存储

注意存储顺序1 空值位图,2 定长属性值,3 变长属性的索引记录(起始位置,长度),每个索引长度固定为4 <有点像指针的思想> ;4 变长属性的具体值

空值位图:指示哪一位为空(NULL),为空的那一位计为1【如下图,第三位为1,就是第三个信息计为NULL】

  • 行存:适合点查询(目标是精准定位 “单个或极少特定记录”)和删改
  • 列存:适合对部分属性的大量查询,减少读负载上的IO使用

缓冲池:对应书 5.9节

CLOCK置换算法:只需要一个bit位作为标记位就可以大概实现LRU的效果

想象有一个环形的时钟,每个时钟位置都可以放置一个数据页。当数据页被访问时,标记位就从0变成1(但此时时钟指向的位置不变)。

当缓存空间满了,需要淘汰一个数据页时,算法就从当前指针位置开始,沿着时钟方向逐个检查数据页。如果一个数据页没有被标记,那么就把它淘汰掉,因为这意味着它最近没有被使用过。如果遇到被标记的数据页,就把它的标记清除(熄灭灯),然后继续检查下一个数据页,直到找到一个没有标记的数据页并淘汰它,替换成新的页面,并将新的页面记为0(调入后再执行缺页指令,才访问这个页面,然后将其置为1)。(理解:如果初始值设成1的话会出问题)

这里涉及到操作系统中 “页面调入” “页面访问” 的时序区别。在 CLOCK 算法中,新页面初始标记为 0 而非 1 的核心原因是:页面调入内存的操作本身不等于对该页面的访问

  1. 页面调入(Page Fault Handling)
    当发生缺页中断时,操作系统需要从磁盘将所需页面调入内存。这个过程分为两个步骤:

    • 步骤 1:物理页面分配
      系统选择一个空闲帧(或淘汰一个旧页面),将新页面从磁盘复制到该帧。此时,新页面只是被加载到内存中,但尚未被 CPU 访问
    • 步骤 2:访问权限恢复
      当页面加载完成后,CPU 会重新执行导致缺页的指令,此时才会真正访问该页面(读 / 写数据)。

    关键点:CLOCK 算法在步骤 1(页面加载)时就将新页面的标记位设为 0,而访问标记(设为 1)是在步骤 2(CPU 实际访问)时才发生的。

索引的分类

根据存储的物理结构

  • 聚簇索引:索引和数据绑定在一起,按照索引顺序存储(如果索引对应了n个元组,则这n个元组应该连续存储)
  • 非聚簇索引:索引和数据分开存储

根据索引是否覆盖所有的搜索码

  • 稠密索引
  • 稀疏索引

根据是否按照数据的顺序存储

  • 顺序索引

注意:上面几个没有绝对的包含关系,而是不同层面的实现形式

B+树

查找的复杂度与树的深度线性相关

各方面的数据限制

注意:对叶子节点是元素的要求,但对非叶子(根节点和中间节点)节点是指针数量(孩子)的限制

插入&删除算法

都是先用find找到要操作的叶子节点,然后操作

注意,插入并不是简单放在空位上,还要对节点内部进行排序

潜在问题和解决:

  • 插入导致过满?拆分,拆分会反应到父节点改变,存在网上级联影响的可能
  • 删除导致元素过少?先尝试和左右兄弟节点合并(旁边兄弟有足够空位),合并会反应到父节点改变,存在网上级联影响的可能
  • 当删除导致节点元素过少时,优先尝试与兄弟节点合并(如果兄弟节点有足够空间)。
  • 合并后,父节点的指针需要重新调整(例如,两个子节点合并为一个,父节点原本指向这两个节点的指针需要合并为一个),这就是指针重分布

B+树的查询

说明:

  • node.P_{i} 指左边指针对应的子树
  • node.P_{i+1} 指右边指针对应的子树
  • P_{n} 为最右边的子树(确定的)

哈希表

先计算该项对应的哈希值,作为插入的索引

两类哈希表&静态哈希表的两种实现形式

静态哈希表的 “静态” 体现在其哈希表的结构和大小在初始化后就不再改变 。哈希表的桶数量固定,使用的哈希函数也是确定不变的 。静态哈希表若要应对超出当前容量的数据,往往需要重建整个哈希表,开销较大 。动态哈希表则具备相应机制,如可扩展哈希表通过按需扩展目录深度、分裂桶等方式来容纳更多数据;线性哈希表以线性方式逐个增加桶来实现扩容 。

动态【下图中】

i 位二进制,可以映射到 2^{i} 个哈希桶

右上角小的 j = 1,2代表“局部深度”,代表用 j 位二进制就可以识别到,比如00和01合并以后,用一位“0”就可以确定是第一个桶,所以是 j = 1

可扩展哈希表~插入操作(插入但当前桶满?)

先计算要插入项对应的哈希值

然后尝试映射到对应的桶,看这个桶是否已经满了,若满了,进行下面判断:

  • j<i (说明有桶合并了):展开合并的桶
  • j=i: 进行扩容(i值增加一位,原有的哈希桶重新映射到新的目录上去)

缺点:可能总是因为某一个桶满,导致所有桶都要扩展,致使填充率降低【下面未解决】

删除键值:墓碑标记(p113)

线性哈希表(利用哈希值的后m位)【优化了上面的缺点】

具体原理公式见下图

以插入1111为例,如果一开始没有对应11索引,11B = 3D,3-2^{m-1} = 1,所以先存在01桶

汲取可扩展哈希表的分裂桶经验,但以插入后填充率超过阈值作为判断,若超过,加新桶,分裂旧桶

避免全表扩展:由于不是某个桶满就全表扩展,而是关注整体填充率,所以不会因个别桶的情况轻易对整个哈希表进行操作。当填充率未超阈值时,即使有桶满了,也不会触发全表扩展,减少了不必要的空间浪费,维持了相对较高的填充率 。

哈希表适合点查询,B+树进行范围查询更方便

第三章:

关系代数:\pi,\sigma,除法和多表查询

//去重问题:根据豆包的说法,一般投影运算pai会自动去重,但是sigema不会对元素去重

//注意:一般要综合使用,比如要选出“选出运送了1号货物的商家的编号”,应该先用sigema选出“1号货物”,再用pai选出商家编号 

多表查询:查询的表为多个表的自然连接

sigema的角标“且,或” 的表示:用析取合取号

下图二:除法的使用

看右图用例比较方便,注意除法变换不可逆(乘法是包含关系)

除法使用例:找出至少使用了S1提供的全部零件的JNO<看到“全部”,想到除法>

<=> 找出S1提供的全部零件(用sigema_S1再用pai选出零件列), 找出包含全部零件和JNO映射关系的列(用pai选出),最后用后者除以前者。

自然连接的定义&特殊的自然连接 

左外连接(Left Outer Join)的结果中,左边表的所有记录都会保留,而右边表中与左边表匹配的记录会正常显示,右边表中缺失的匹配项对应的字段值填 NULL

SQL语句

课本索引:

表、视图的创建

字符串匹配 LIKE【p115】

聚集函数【p117】

GROUP BY【p118】

嵌套查询【p125】 where Sno in (select Sno where 。。。)

数量关系上的查询(如最值) 运算符和聚集函数如 any, <max,>min【p128】

集合操作 Union Intersect Except 等【p129】

数据控制语言【权限授予等p134】

使用Exists语句之前:将自然语言最终转换成 “(不)存在。。。样的的记录” 的形式

嵌套查询:注意父子之间的连接:“他”<也即父查询“输出他的”,子查询“不存在他的”>,所以要记得在子查询的where语句中加入Sno = Student.Sno 的判断

用 Exists 语句实现全称量词:两次嵌套

选修全部课程 <==> 不存在这样的课程,不存在该同学对该课程的选修记录<p29>

典例 tip:记住就好了~

下面语义:这样的学生,不存在 95002 选修的课程,言对于该学生而,不存在自己选修该课程的记录

理解:SCX,SCY,SCZ分别是不同层次对表的代称,其中SCY用来指代‘95002’号学生,SCX和SCZ都是用来指最终要输出的学生,层次关系是:SCX和SCZ学号相等,SCX和SCZ 一起于

下面的前两层都很好理解,关键在于怎么理解第三层与前两层的对应

第二层where中限制了学生是95002,从而第三层中SCY.Cno是95002 选过的

换一种方式实现上面的功能【Group by + Having】

一样的课程 <=> 找出我选了你也选了的课程,发现数量是一样的

SELECT SCX.Sno
FROM SC SCX
WHERE SCX.Cno IN (SELECT Cno FROM SC WHERE Sno = '95002')--现在只留下了两人都选的课程
GROUP BY SCX.Sno    --用group by A,则select后面要么是A,要么是对于各列的聚集函数
HAVING COUNT(DISTINCT SCX.Cno) = (SELECT COUNT(DISTINCT Cno) FROM SC WHERE Sno = '95002');
--select 后面的东西本质上就是这一部分查询最终输出的东西

select scx.sno
from sc as scx
where sc.cno in (select cno from sc where sno = '95002')
group by scx.sno
having count(distinct scx.sno) = (select count(distinct cno) from sc where sno = '95002')

注意Distinct在聚集函数<比如count>中的使用 

例题:查询同时选了两个指定课程的学生ID

【利用子查询】

SELECT ID
FROM takes
WHERE course_id = 'CS-101'
  AND ID IN (SELECT ID FROM takes WHERE course_id = 'CS-190');

SQLSheet系统注意事项
  • 表格重命名要用as,“from student ST”会出错,只能用 from student as s, 且一旦重命名就不能再用原始名字!
  • 注意去重,查询要去重DISTINCT,习惯性select DISTINCT ID,注意:DISTINCT不能用于多个列
  • 多表连接,select 对象要指明,比如from的多个表都有ID列,select的时候不能写select ID,而要具体写比如student.ID
  •  

    画下划线的是表的主码,即这几列的整体才能唯一确定一个元组,也即单独几列不可以唯一确定一个元组

易错示例

注意from的范围:选了课程的学生 != 总学生

注意:下面这种查询对应的是“takes中没有选course_id 的学生”,而不是题目要求的“(所有学生中)没有选course_id 的学生”

Q8 [Query the student ID of students who have taken all courses]

select distinct student.ID
from student, takes
where student.ID = takes.ID
and takes.course_id in (select distinct course_id from takes)
group by student.ID
having count(takes.course_id) = (select count(distinct takes.course_id) from takes);

 T16

误!题目理解错误!number of courses是指每个符合要求学生的具体85分以上课程的数量,并不是“总共有多少个85分以上课程(所有课程满分都💯哇!)”

后记

SQL是一种声明式语言,而不是一种过程式语言,即用户只指出做什么,但不指定怎么做(不描述执行的具体步骤)

但任何一个SELECT语句,都可以等价转化为关系代数 \pi \sigma 的语句,后者是具有一定过程性的(先做笛卡儿积,再用\sigma筛选,最后\pi投影) 

关系代数的局限性:关系代数算子的实现不清楚(实际上要落实到具体的物理操作符上)

第六章

总体脉络

查询解析

八大rules

(A ::= B 表示了一种映射关系,如果有B,那么就映射为A并存储)

如下图所示,A一般是类型,B一般是SQL具体的语言原子

逻辑重写

提高查找效率的方式:谓词拆分 & 投影下推(见下段)& 笛卡尔积转成等值连接来进行操作(两个表都有C列,笛卡儿积里面可能有两个C列不相同的合并到同一行)

谓词拆分 & 投影下推:凡是只涉及单表的操作,都可以放到前面去完成,比如多个σ,实际上选择操作可以拆分在单个表上完成,于是可以在笛卡儿积前先筛选

关于嵌套查询的处理方法

  • 下图:【重写】将原始嵌套查询重写为连接查询
  • 【实体化临时表】对于同一个复合表(如A join B on..)的多次查询,实体化只要进行一次聚合操作(然后讲这个聚合的结果就存下来了),反之就要每次查询都聚合。所以这个实体化的优势在于多次查询,单次查询的话实不实体化没区别【下图未明显体现】

三. 物理算子(查询算子的具体实现)

查找的两种方式

两个变量的定义:B, T【关系的块数,元组数】

选择率:满足这个条件的元组数/ 总的元组数

  1. 关于查找唯一项的处理(=)
  2. 关于查找范围的处理(>,<等比较谓词)

对于有序表

  • 查找>=v: 找到v,向右扫
  • 查找<=v:直接从头节点开始,向右扫,直到找到v

两类索引及其查找特点

  • 聚簇索引(叶子节点包含索引和数据块):先定位到 v 对应的叶子节点(经过 h 次查找,h为索引对应的B+树的树高 ),然后从该叶子节点指向的数据块开始顺序扫描后续所有数据块 。由于数据按索引顺序物理存储,后续数据块在磁盘上大概率是相邻的,是顺序 I/O  。
  • 非聚簇索引(叶子节点只有索引):先定位到 v 对应的叶子节点(h 次查找 ),之后顺序扫描叶子节点来提取所有指向记录的指针 。因为非聚簇索引叶子节点只存指针,需根据指针再去访问实际数据块,是随机 I/O

各类查询算法的平均复杂度

  • 搜索码不唯一:即可能有多个,找到一个不能保证剩下没有了
  • 表扫描(Table Scan):遍历整张表的所有元组(记录)来查找满足条件的数据。比如要在学生信息表找年龄大于 20 岁的学生,全表扫描就会把表中每个学生记录都看一遍。
  • 索引扫描(Index Scan):利用已建立的索引来查找数据。索引类似书本目录,能快速定位数据位置。
  • 选择率S_p(R):满足谓词条件 p 的元组数量与关系 R 总元组数量的比值 ,衡量满足条件数据在全表中的占比。
  • B(R):关系 R 占用的块数 ,这里表示表数据占用磁盘块数量。
  • T(R):关系 R 中的元组(记录)总数 。
  • 聚簇索引:数据物理存储顺序索引顺序一致,查找时可利用顺序 I/O 特性,性能较好 。
  • 非聚簇索引:索引顺序与数据物理存储顺序不同,查找时涉及随机 I/O ,相对复杂。
  • 二叉树相关:对于一棵二叉树(分支因子n = 2 ),如果树高为h ,最多能存储\(2^h - 1\)个节点(满二叉树情况)。当要查找一个节点时,最多需要比较h次(也就是树的层数)。如果有N个节点,在平衡二叉树中,树高h\approx\log_2 N ,这就是对数和查找次数相关的基本原理。在下面的情景中,N就是元组数T(R)

连接(PPT中p95开始)

I/O操作是对应取表(页面)操作,下面用T(R)是因为在当前逻辑下,对于每个R中元组,都要取出一次页面,所以数量是T(R)

注:【比较后的写回都不考虑IO,但排序/ 哈希后的写会考虑IO?】

嵌套循环

关于B(R)的说明:将R中的每个页面逐一拉进内存(由此产生了B(R)),为后续比较做准备

左图算法:对每个元组,都将S中的一个页面拉进内存

右图(优化):对R的每一个页面,将当前页面中的所有元组,和S中拉进来的页面都做了比较以后,再拉入下一个S的页面

注意:小一点的表作外表(外层循环)

对于内存空间较大/ 或者有缓冲池的情况:

缓冲池相当于主存的扩容弹匣

因为比较只能在主存中进行,并且要求比较的双方(外表的页面& 内表的页面)都在主存中

所以,一次拉入S页面,能比较的R页面越多,拉入S的页面次数就越少

PS:预留的两帧:一帧存S的页面,一帧存比较的结果(比较结束后,这一帧的结果写回外存)

PS:这个叫做“写回外存”

排序合并

排序和哈希都是基于“先通过某种方式将相等的元组聚集,再进行等值连接”的思想

关于排序R的cost说明:

第一次操作(数据分块与内排序)

  • 把表 R 的数据按块读入内存进行内排序。这里确实涉及对每一块数据进行一次内排序操作。总共有 B (R) 块数据,每一块都要经历读入内存、内排序、写回磁盘的过程。这一步的主要开销是将 B (R) 块数据依次处理,从磁盘 I/O 角度看,读入和写回操作整体上相当于对 B (R) 块数据进行了一次读写,再加上每一块内排序的计算开销(可近似看作一次操作对应一块数据 ),所以从成本计算角度,这部分开销和 B (R) 相关 。

第二次操作(归并阶段)

  • 归并过程类似二叉树的合并。将已经内排序好的 B (R) 个有序子块进行归并,构建一棵归并树(类似二叉树结构 )。在这棵归并树中,每一层的归并操作都涉及对一些子块的合并。
  • 假设子块数量为 B (R) ,那么归并的趟数近似于以 2 为底 B (R) 的对数(向上取整 ),即⌈log₂B (R)⌉ 。在每一趟归并中,又要对 B (R) 块数据进行处理(因为最终要把所有子块合并成一个有序序列 ),所以归并阶段的 I/O 开销就是 B (R) * ⌈log₂B (R)⌉ 。

考不考虑写回的Io呀,我觉得是一倍的B(R)*(1+log)呀

把这两部分开销综合起来,就是总的排序成本 2B (R) × (1 + ⌈log₂B (R)⌉) ,其中 2B (R) 体现了两遍磁盘操作(读入分块排序、归并写出 ),(1 + ⌈log₂B (R)⌉) 分别对应内排序和归并阶段的相关操作次数。 你之前的理解准确抓住了关键的两个操作阶段和对应的开销影响因素呢。

哈希连接

用相同的哈希函数,将R,S映射到桶里面,再分别在桶里面进行比较 

2(BR+BS):将R,S的页面分别拉进主存哈希运算后,写回外存【此时元组可以视作已经被映射到同一个桶里面了】

1(BR+BS):将每个哈希桶拉进主存,进行比较【这里没考虑写会IO】

三种连接方式效率的比较

注意:只有嵌套循环能实现 \theta 连接【对应非等值连接】

要实现效率最大化,需要提前知道B(R),B(S)的值,带入运算后比较

开销模型p160左右

对于两种假设造成的选择率的计算偏差的解决方法

  • 对于均匀分布假设:通过统计直方图
  • 对于属性独立假设:通过选择性地收集某些列的信息

对于上面这种复合谓词计 v:

  • 连续合取:选择率 = 选择率 i 的乘积【这些就是基于”属性之间是相互独立的“假设运算的】
  • 连续析取:选择率 = 1 - 选择率i的乘积

p169

基于规则的优化(RBO)【逻辑重写】

  • 定义与原理:通过查询的逻辑重写,依据关系代数的等价变换规则,减少不合理开销。它不依赖具体关系实例。

  • 目的:利用既定规则对查询进行优化,在逻辑层面提升查询效率。

基于代价的优化(CBO)【代价估计& 选代价最小的】

为了提前知道哪种操作比较合适,需要提前统计一些数据的信息,统称 :“统计信息”

左深树【所有节点的右节点都是叶子节点】的数量比浓密树小不少

  • 定义与原理:侧重于查询的物理优化,运用代价模型对操作进行代价估计,生成由物理操作符组成的物理执行计划(树 ),且与关系实例相关。

  • 目的:从物理执行角度,通过评估不同操作代价,选择较优执行方案。

RBO 与 CBO 结合

  • 现状与问题:二者结合在查询优化中占比高,甚至占据整个查询响应时间一半以上,但由于是 NP 问题,无法确保找到最优执行计划 (只能相对优化),不过仍是必要手段。
课后实验

解释型执行器【PPTp173开始】

物化模型:每一步操作后的结果存下来,作为下一步执行的输入

优化:边执行边尝试合并,输出一个结果,就尝试合并,不需要等到所有都处理完

火山模型:逐元组执行

优化:上一个菜就开吃,可能不够所有人吃,应该按照人数估计大概上几个菜开吃比较合适

向量化模型:按向量执行【一组元组】(攒够一定规模再去执行)

编译型执行

即时编译:根据具体的数据情况生成查询代码,更有针对性

编译型和解释型区别 

  • 执行过程
    • 编译型先编译后执行,执行前完成翻译工作,生成可独立运行的可执行文件 。
    • 解释型:程序运行时,由解释器逐行读取源代码,逐行翻译成机器可执行指令并立即执行,不生成独立可执行文件 。例如 Python 程序运行时,Python 解释器逐行处理代码 。
  • 执行效率
    • 编译型:执行速度快、效率高,运行时无需额外翻译开销 。
    • 解释型:逐行解释执行,每次运行都要翻译,存在额外性能开销,执行速度相对较慢 。不过部分解释型语言采用即时编译(JIT)等技术提升了效率 。
  • 开发效率与灵活性
    • 编译型:开发过程中编译环节耗时,修改代码后需重新编译,开发效率相对低,代码修改不灵活 。
    • 解释型:开发灵活,代码修改后可立即运行,无需繁琐编译过程,适合快速原型开发和小型项目,开发效率相对较高 。

问题

还是没太理解二者的区别

编译型执行

即时编译:根据具体的数据情况生成查询代码,更有针对性

有i个索引和有i个索引查找方式有什么区别【A:注意区别查找和连接!查找是对一个表进行的,有i个索引就是i个列上有索引,那么查找就可以分别从一个列的索引进去,进行初步筛选,再在剩下的元组里面看其他列,所以对应了i种索引入手的查找方式,外加一种全表查找:每一行,看条件中的所有筛选项】

第七章

事务:一个程序执行单元,要执行就要一起执行(原子性)【BEGIN ... COMMIT】,否则返回原始状态【BEGIN ... ROLLBACK】

数据库事务管理要解决的两个核心问题

  1. 如何应对系统崩溃(后的恢复):
    1. 日志的作用:数据库使用日志记录事务对数据的所有修改操作,而不是简单的 “记事本”。日志按时间顺序记录每个事务的开始、结束以及对数据的修改细节,包括修改前的值(旧值)和修改后的值(新值)等。日志记录先于实际数据修改写入持久存储【预写日志】,这是保证数据可恢复的关键。
    2. 恢复过程:当系统崩溃后进行恢复时,数据库系统会读取日志。【对于(用户界面显示)已经提交的事务(但是内核中可能并没有执行完成),系统会根据日志中的记录重新执行对数据的修改操作,将数据恢复到事务提交后的状态,这称为重做(Redo)操作;【对于未完成(未提交)的事务<完成了一半,比如执行了A(根据持久性原则,已经写到了A的外存),但还没有操作B】,系统会根据日志中的旧值将数据回滚到事务开始前的状态,撤销这些未完成事务对数据的部分修改,这称为回滚(Undo)操作。【例如,一个事务对某条数据进行了多次修改但还未提交时系统崩溃,恢复时就利用日志中的旧值把数据恢复到事务开始前的状态,避免未完成事务对数据一致性的破坏。
    3. 说明:上面两种操作,本质是要保证外存的存储和用户界面的效果是一致的
  2. 多事务并发执行

事务的要求【ACID】

  • A原子性:一个事务内的操作,要执行就要一起执行
  • Consistence一致性要求:修改数据前后,数据之间的核心关系应保持一致,执行过程中,允许暂时的不一致,比如转账,总额不变
  • Isolation隔离性要求:本质:从每条数据的角度来讲,单独访问得到的结果和与其他事务并发执行的结果应该一致
  • Durable持久性要求:只要A,B有一个执行完了【不要求事务执行完成】,就要被固定下来【反馈到外存的持久性存储上】(但要注意,是说每次修改完就有写回操作,但是若调度失误,存在A还没有被写回的时候,A在外存中的数据就被读出的情况)

其中:

  • I(隔离性):并发控制完成
  • AID:影子页面,日志; 且由DBMS自动完成
  • C:由事务语句的编写人完成

冲突等价& 冲突可串行化

视图等价p30:关注首尾,和中间顺序

视图等价要求:说白了,开始,中间和结束的一致性

  • 开始呀,你读我也读、
  • 最后呀,你写我也写
  • 中间呀,你读的值和我读的要一样

最先读谁,最后写了谁,中间读操作关系(谁读另一个写的值,若中间没有读操作,省略),二者保持一致【满足这个定义即可】

每个冲突可串行化都是视图可串行化的

判断冲突可串行化的方法

有向边:凡是有写就是


破坏一致性:脏读和丢失更新【相关几个概念】

调度:多个操作穿插执行

脏读(Dirty Read)【还没提交】

  • 定义:指一个事务读取了另一个未提交事务修改的数据。这就像是在一本书还没写完校对就被别人拿去阅读,可能读到的是错误或不完整的内容。
  • 示例:假设有两个事务,事务 A 和事务 B。事务 A 修改了某条数据,但还未提交;此时事务 B 读取了被事务 A 修改后的数据。如果事务 A 随后回滚了操作,那么事务 B 读取到的数据就是无效的 “脏数据”。比如在银行系统中,事务 A 将账户余额从 1000 元改为 1100 元但未提交,事务 B 读取到余额为 1100 元。若事务 A 回滚,账户实际余额还是 1000 元,事务 B 读取的 1100 元就是脏数据,可能导致后续业务逻辑出错。

丢失更新(Lost Update)

  • 定义:两个或多个事务同时对同一数据进行更新操作,其中一个事务的更新结果被其他事务覆盖,造成部分更新丢失。这类似于多人同时修改一份文档,后保存的人覆盖了前面人的修改。
  • 示例:假设事务 A 和事务 B 都读取了账户余额为 1000 元。事务 A 将余额增加 100 元并提交,此时余额变为 1100 元;事务 B 也进行同样的增加 100 元操作,但它是基于最初读取的 1000 元进行计算的(比如A的执行后还没有来得及将数据写回外存,就被B给读出了),所以事务 B 提交后,余额还是变为 1100 元,事务 A 增加的 100 元就被 “丢失” 了。正常情况下,两个事务都增加 100 元,余额应该是 1200 元,但由于丢失更新,少了 100 元的更新效果。

ps:幻读:数据库的幻读是指在同一事务中,对于同一个数据项,一个进程在写小范围,一个进程在读所有项,两次读取同一范围的记录时,第二次读取发现有新的符合条件的记录被插入,使得两次读取结果不一致的现象 。

并发控制p42

锁【互斥X(DW)、共享锁S(D)】,时间戳【两个时间戳维护读时间和写时间】

延迟解锁的问题

2-PL能避免饿死,但是不能避免死锁【不考】

 一旦开始解锁,不能再申请新锁

允许锁转换:申请阶段可以升级,释放阶段可以降级

  • 深蓝:已经持有(锁)
  • 浅蓝:等待分配(锁)

  • 悲观方式:从源头就检查
  • 乐观方式:认为基本没冲突,先让你们无差别执行,执行后,在提交时再检查    

乐观的并发控制p67

时间戳可以看作进程执行相对于开始执行的时间,越晚执行的进程时间戳越大

日志📝【关键记录四个数据:事务,数据项,旧值,新值】


冲突:不满足隔离性【单独执行的结果和一起执行的结果没有区别】

冲突总的来看源自写操作,冲突的对象一定被执行过写操作【必要条件】

章节5.2中的PPT上不熟悉的部分p49起        

章节四的开始p64

END PTR


网站公告

今日签到

点亮在社区的每一天
去签到