本文是《数据库系统概念》一书的阅读笔记,会侧重于数据原理,并且会忽略掉很多细节的知识。
第十四章:事务
事务需要满足以下性质
- 原子性:事务的所有操作在数据库中要么全部正确反映出来,要么完全不反映。
- 一致性;隔离执行事务时保持数据库的一致性。
- 隔离性:每个事务都感觉不到系统中有其他事务在并发地执行。
- 持久性:一个事务成功完成后,它对数据库的改变必须是永久的,即使出现系统故障。
事务原子性和持久性
事务必须处于五种状态之一
- 活动的(active):初始状态,事务执行时处于这个状态。
- 部分提交的(partially committed):最后一条语句执行后。
- 失败的(failed):发现正常的执行不能继续后。
- 中止的(aborted):事务回滚并且数据库已恢复到事务开始执行前的状态后。
- 提交的(committed):成功完成后。
提交或者终止称为结束。
可串行化
- 只考虑read和write两种操作,只有在I与J全为read指令时,两条指令的执行顺序才是无关紧要的。当I与J是不同事务在相同的数据项上的操作,并且其中至少有一个是 write指令时,我们说I与J是冲突(conflict 的。
- 如果调度S可以经过一系列非冲突指令交换转换成S',我们称S与S'是冲突等价 (conflict equivalent)的。
- 若一个调度S与一个串行调度冲突等价,则称调度S是冲突可串行化 (conflict serializable)的。
- 可以利用优先图是否存在环判断调度是否满足冲突可串行化,图中的顶点由事务构成,边集由满足下列三个条件之一的边
组成:
- 在
执行 read(Q) 之前,
执行 write(Q) 。
- 在
执行 write(Q) 之前,
执行 read(Q) 。
- 在
执行 write(Q) 之前,
执行 write(Q) 。
- 在
事务隔离性和原子性
- 可恢复调度:对千每对事务
和
,如果
读取了之前由
所写的数据项,则
先于
提交。
- 无级联调度:对于每对事务
和
,如果
读取了先前由
所写的数据项,则
必须在
这一读操作前提交。
事务隔离性级别
- 可串行化(serizable):通常保证可串行化调度。
- 可重复读 (repeatable read):只允许读取已提交数据,而且在一个事务两次读取一个数据项期 间,其他事务不得更新该数据。
- 已提交读(read committed) : 只允许读取已提交数据,但不要求可重复读。
- 未提交读(read uncommitted):允许读取未提交数据。
第十五章:并发控制
基于锁的协议
加锁的类型有两种
- 共享的(shared):如果事务
获得了数据项 Q 上的共享型锁 (shared-mode lock) (记为 S),则
可读但不能写 Q 。
- 排他的(exclusive):如果事务
获得了数据项 Q 上的排他型锁 (exclusive-mode lock) (记为 X),则
既可读又可写Q 。
事务在等待锁的过程可能会出现饿死的情况,可以采用策略避免:当事务 申请对数据项 Q 加 M 型锁时,并发
控制管理器授权加锁的条件是:
- 不存在在数据项 Q 上持有与 M 型锁冲突的锁的其他事务。
- 不存在等待对数据项 Q 加锁且先于 T, 申请加锁的事务。
- 两阶段封锁协议要求锁操作分为两阶段
- 增长阶段(growng phase):事务可以获得锁,但不能释放锁。
- 缩减阶段(shrinking phase):事务可以释放锁,但不能获得新锁。
- 严格两阶段封锁协议求事务持有的所有排他锁必须在事务提交后方可释 放。
- 强两阶段封锁协议它要求事务提交之前不得释放任何锁。
死锁处理
预防死锁有两种方法:
- 通过对加锁请求进行排序或要求同时获得所有的锁来保证不会发生循环等待。
- 每当等待有可能导致死锁时,进行事务回滚而不是等待加锁,例如锁超时回滚。
死锁检测可以使用等待图来检测,当检测到死锁后,系统选择牺牲一个事务进行回滚。
多粒度
每个数据库操作的粒度是不同的,因此设计一种更加灵活的多粒度锁。例如,将数据库->区域->文件->记录组成成以下的层级关系:
- 可以给任意节点加锁;
- 若事务
给图中的
显式(explicit lock)地加锁,则事务
也给所有属于该文件的记录隐式(implicit lock)地加锁;
- 在一个结点显式加锁之前,该结点的全部祖先结点均加上了意向锁。
- 多粒度协议要求加锁按自顶向下的顺序(根到叶),而锁的释放则按自底向上的顺序(叶到根)。
基于时间戳的协议
时间戳的协议将事务和一个唯一的时间戳联系起来,时间戳可以使用系统时钟或者逻辑计数器实现。若,则系统必须保证所产生的调度等价
于事务
出现在事务
之前的某个串行调度。同样,数据项也需要和数据戳进行关联:
- W-timestamp(Q) 表示成功执行 write(Q) 的所有事务的最大时间戳。
- R-timestamp(Q) 表示成功执行 read(Q) 的所有事务的最大时间戳。
时间戮排序协议 (timestamp-ordering protocol) 运作方式如下:
- 假设事务
发出 read(Q) 。
- 若 TS(
) < W-timestamp(Q), 则
需要读入的 Q 值已被覆盖。因此, read 操作被拒绝,
回滚。
- 若 TS(
) >= W-timestamp(Q), 则执行 read 操作, R-timestamp(Q) 被设置为 R-timestamp(Q)与 TS(
) 两者的最大值。
- 若 TS(
- 假设事务
发出 write(Q) 。
- 若 TS(
) < R-timestamp(Q), 则
产生的 Q 值是先前所需要的值,且系统已假定该值不会再产生。因此, write 操作被拒绝,
回滚。
- 若 TS(
) < W-timestamp(Q), 则
试图写人的 Q 值已过时。因此, write 操作被拒绝,
回滚。
- 其他情况,系统执行 write 操作,将 W-timestamp(Q) 设置为 TS(
) 。
- 若 TS(
Thomas写规则忽略过时的 write 操作来提高并发性:
- 若 TS(
) < W-timestamp(Q), 则
试图写人的 Q 值已过时。因此,这个 write 操作可忽略。
基于有效性检查的协议
有效性检查协议 (validation protocol) 要求每个事务按两个或三个阶段执行:
- 读阶段,对应时间戳Start(
)
- 有效性检查阶段,对应时间戳Validation(
)
- 写阶段,对应时间戳Finish(
)
事务的有效性测试 (validation test) 要求任何满足 TS(
) <TS(
) 的事务必须满足下面两条件
之一:
- Finish(
) < Start(
) 。
所写的数据项集与
所读数据项集不相交,并且
的写阶段在
开始其有效性检查阶段之前完成 (Start(
) < Finish(
) < Validation (
)) 。
多版本机制
时间戳排序协议可以扩展为多版本的协议。对千每个数据项Q,有一个版本序列<,
, …
>与之关联,每个版本
包含三个数据字段:
- Content:是
版本的值。
- W-thnestamp(Q):是创建
版本的事务的时间戳。
- R-thnestamp(Q):所有成功地读取
版本的事务的最大时间戳。
多版本时间戮排序机制 (multiversion timestamp-ordering scheme)运作如下:假设事务发出 read(Q) 或 write(Q) 操作,令
表示 Q 满足如下条件的版本,其写时间戳是小于或等于 TS(
) 的最大写时间戳。
- 如果事务
发出 read(Q),则返回值是
的内容。
- 如果事务
发出 write(Q),且若 TS{
) < R-timestamp(
), 则系统回滚事务 T,. 另一方面,若 TS(
) = W-timestamp{
), 则系统覆盖
的内容;否则(若 TS(
) > R-timestamp(
)), 创建Q的一个新版本。
快照隔离
从概念上讲,快照隔离在事务开始执行时给它数据库的一份“快照"。更新操作发生在事务的私有工作空间中,直到事务成功提交,此时更新写入数据库。两个并发的事务可能会覆盖对方的写入,可以使用先提交者获胜和先更新者获胜策略避免。
插入操作、删除操作与谓词读
- 除了read和read,read/write/insert/delete相互以及自身之间都是冲突的。
- 对于删除操作
- 在两阶段封锁协议下,一数据项可以删除之前,在该数据项上必须请求加排他锁。
- 在时间戳排序协议下,
- 如果 TS(
) < R-timestamp(Q), 则
将要删除的 Q 值已被读取。因此,delete 操作被拒绝,
回滚。
- 如果 TS(
) < W-timestamp(Q), 则有事务已经写过 Q 因此, delete操作被拒绝,
回滚。
- 否则,执行delete 操作。
- 如果 TS(
- 对于插入操作
- 在两阶段封锁协议下,如果 T, 执行 insert(Q) 操作,就在新创建的数据项 Q 上赋予
排他锁。
- 在时间戳排序协议下,如果 T, 执行 Insert(Q) 操作,就把 R-timestamp(Q) 与 W-timestamp (Q) 的值设置成 TS(
)。
- 在两阶段封锁协议下,如果 T, 执行 insert(Q) 操作,就在新创建的数据项 Q 上赋予
第十八章:并行数据库
I/O井行
水平分区(horizontal partitioning)中,关系中的元组划分(或分散)到多张磁盘上,使得每个元组驻留在一张磁盘上。常用的划分方式有:
- 轮转法(round-robin):适合顺序查询;
- 散列划分(hash partitioning):适合点查询;
- 范围划分(range partitioning):适合点查询和范围查询,但是容易造成执行偏斜。
查询间井行
在查询间井行 (interquery parallelism) 中,不同查询或事务彼此并行地执行。然而,需要锁机制来保证高速缓存一致性:
- 事务对一个页面进行任何读或写访问之前,先用相应的共享或排他模式封锁该页面。
- 在事务释放一个页面的排他锁之前,它将该页面刷新到共享磁盘中,然后释放封锁。
查询内井行
查询内井行 (intraquery parallelism)指的是单个查询在多个处理器和磁盘上并行执行。包括:
- 操作内井行 (intraoperation parallelism):并行地执行每个运算,如排序、选择、投影和连接。
- 操作间井行 (interoperation parallelism):并行地执行一个查询表达式中的多个不同的运算。
操作内井行
并行排序
- 如果关系刚好在排序的属性上进行了范围划分,可以分别对每个分区进行排序,然后把各个排序结果连接起来。
- 范围划分排序:
- 采用范围划分策略对关系中的元组进行重新分布;
- 每个处理器在本地对关系中属千自己的分区进行排序,不与其他处理器交互。
- 并行的外部排序归并:
- 每个处理器
在本地对磁盘
中的数据进行排序。
- 然后,系统对每个处理器已排好序的归并段进行合并,得到最终的排序输出。
- 每个处理器
井行连接
- 基于划分的连接:系统将关系 r 和 s 分别分成 n 个分区,送到各个处理器,并在本地进行其连接计算。可以进行范围划分,或者散列划分。
- 非对称的分片——复剩连接:
- 系统对其中一个关系,例如 r, 进行划分。
- 系统将另一个关系 s 复制到所有的处理器上。
- 然后,处理器
采用任何一种连接技术在本地计算
和整个 s 的连接。
- 分片——复制连接:如果s较大,那么同样也要将关系s分区。
操作间井行
- 流水线井行:可以在不同的处理器上同时运行 A 和 B, 使得在 A 产生结 果元组的同时,B 使用它们。
- 独立井行:查询表达式中互不依赖的运算可以并行地执行。
第十九章:分布式数据库
分布式数据存储
关系在分布式数据库中存储主要有两种方法:复制 (replication) 和分片 (fragmentation) 。其中,复制可以提高可用性、增加的并行度,但是同时增加更新开销。数据分片可以使用水平分片或垂直分片,分片要保证数据透明性。
分布式事务
分布式事务需要两个子系统实现:
- 事务管理器 (transaction manager):管理那些访问存储在一个局部站点中的数据的事务(或子事务)的执行。
- 事务协调番(transaction coordinator):协调在该站点上发起的各个事务(既有局部的也有全局的)的执行。
分布式系统可能会遭遇如下故障:
- 站点故障。
- 消息丢失。
- 通信链路故障。
- 网络划分。
提交协议
考虑一个从站点发起的事务T,设
的事务协调器是
。
两阶段协议
当T完成其执行时,启动2PC协议。
- 阶段1。
将记录<prepare T>加到日志中,接着它将一条 prepare T 消息发送到执行 T 的所有站点上。站点上的事务管理器确定它是否愿意提交 T 中属于它的那部分:
- 如果答案是“不”,事务管理器就把记录 <no T> 加到日志中,然后通过向
发送一条 abort T 消息来作为响应。
- 如果答案为“是”,事务管理器就把记录 <ready T> 加到日志中。然后事务管理器通过向
回复一条 ready T 消息作为回答。
- 如果答案是“不”,事务管理器就把记录 <no T> 加到日志中,然后通过向
- 阶段2。
- 如果
接受到来自所有参与站点的 ready T 消息,那么事务 T 可以提交。如果收到 abort T 或者超时,事务 T 必须中止。
- 根据结论,要么将记录<commit T> 要么将记录 <abort T>加到日志中,并将日志强制写人稳定存储器上。
- 此后,协调器向所有参与站点发送消息 commit T 或 abort T 当站点收到此消息时,它就把此消息记录到日志中。
- 如果
在 2PC 协议的某些实现中,站点在协议第二阶段的最后向协调器发送 acknowledge T 消息。当协调器收到所有站点发来的 acknowledge T 消息时,它就把记录<complete T>加到日志中。
2PC 协议对千不同类型的故障有不同方式的反应:
-
参与站点故障:
- 如果协调器
检测到某个站点发生了故障,它将采取如下行
- 如果站点在用 ready T 消息回答
前发生故障,则协调器假定该站点是用 abort T 消息来回答的。
- 如果站点在协调器接收到从该站点发来的 ready T 消息后发生故障,忽略该站点的故障。
- 如果站点在用 ready T 消息回答
- 当参与站点
从故障中恢复时,
- 日志包含 <commit T> 记录。在这种情况下,该站点执行 redo(T)。
- 日志包含 <abort T> 记录。在这种情况下,该站点执行 undo(T)。
- 日志包含 <ready T> 记录。
- 如果
正在工作,它就通知
关于 T 是否提交或中止的信息。
- 如果
出现故障,
向系统中所有站点发送 querystatus T 消息来进行这一操作。站点收到这样一条消息,就必须查阅其日志来判定 T 是否在该站点上执行过,如果是,还要看 T 是否提交或中止。接着它把这样的结果告知
。如果没有站点提供恰当的信息(即 T 是否提交 或中止),那么
既不能中止 T 也不能提交 T。关于 T 的决定被推迟到 s, 能得到所需信息时为止。
- 如果
- 日志没有包含关于 T 的控制记录(abort、commit、ready) ,执行 undo(T)。
- 如果协调器
-
协调器故障:
- 如果活跃站点在其日志中包含 <commit T> 记录,则 T 必须提交。
- 如果活跃站点在其日志中包含 <abort T> 记录,则 T 必须中止。
- 如果某些活跃站点在其日志中没有包含 <ready T> 记录,中止 T。
- 则所有活跃站点在它们的日志中都有 <ready T> 记录,但没有别的控制记录(如 <abort T >或 <commit T>),活跃站点必须等待
的恢复,这种情况称为阻塞 (blocking) 间题。
-
网络划分:
- 协调器和它的所有参与者处于一个分区中,故障对提交协议没有影响。
- 协调器和它的参与者属千几个分区,假设其他分区中的站点发生了故障。
三阶段协议
该协议通过引人一个额外的第三阶段来避免阻塞,在这个阶段保证至少有 K 个其他站点知道它打算提交事务,因此只要还有一个站点知道事务要提交,就不会产生阻塞的问题。
分布式数据库中的井发控制
- 单一锁管理器:最简单的方案,但是会成为整个系统的瓶颈。
- 分布式锁管理器:每个站点维护一个本地锁管理器,然而死锁处理会非常复杂。
- 主副本:当事务需要封锁数据项 Q 时,它在 Q 的主站点上请求封锁。
- 多数协议:如果数据项 Q 在 n 个不同的站点复制,则封锁请求消息必须发送到存储 Q 的 n 个站点中一半以上的站点。
- 有偏协议
- 共享锁 (shared lock) 。当事务需要封锁数据项 Q 时,它只需要向包含 Q 的副本的一个站点上的锁管理器请求 Q 上的锁。
- 排他锁 (exclusive lock) 。当事务需要封锁数据项 Q 时,它需要向包含 Q 的副本的所有站点上的锁管理器请求 Q 上的锁。
可用性
高可用性 (high availability) 要求分布式数据库必须在几乎所有时 候都能工作。
基于多数方法的分布式并发控制能在发生故障时仍能工作,每个数据对象都存储它的一个版本号来检测最后一次写人它的时间,它以如下方式来更新版本号:
- 如果数据对象 a 被复制到 n 个不同的站点,那么一条锁请求消息必须被发送到存储 a 的 n 个站 点中的一半以上。
- 读操作检查所有已经加锁的副本,并从具有最高版本号的副本读取值。写操作写它已获得封锁的所有副本,并将所有副本的版本号设置为新的版本号。
读一个、写所协议是有偏协议的一种特例也可以实现高可用性,设置读法定人数为 1,并设置写法定人数为 n。
可以使用备份协调器来应对协调器故障,协调器可以使用选举算法选出。例如有威逼算法:假设站点的标识号为i,选出的协调器总是具有最大标识号的活跃站点。