数据库卡复习七

# 问题的产生:

不同的多事务执行方式:

  • 事务串行执行
  • 交叉并发方式 (单处理机系统)
  • 同时并发方式 (多处理机系统)

事务并发带来的问题:

  • 会产生多个事务同时存取同一数据的情况
  • 可能会存取和存储不正确的数据,破坏事务的一致性和数据库的一致性

数据库管理系统必须提供并发控制机制

并发控制机制是衡量一个数据库管理系统性能的重要标志之一

核心问题:在保证一致性的前提下最大限度地提高并发度

并发控制机制的任务:

  • 对并发操作进行正确调度
  • 保证事务的隔离性
  • 保证数据库的一致性

# 并发控制概述

并发操作带来的数据不一致性

  • 丢失修改:两个事务读入同一数据并修改,一个事务的提交结果覆盖另一个事务的提交结果,导致前一个事务的修改丢失
  • 不可重复读:两个事务读取同一数据,后一事务执行更新操作,使前一事务无法再现前一次读取结果
  • 读脏数据:两个事务读取同一数据,其中一个事务由于某种原因被撤销,但此时另一个事务读取了被撤销事务的修改值,即读取了错误数据

记号:

  • R (x):读数据 x
  • W (x):写数据 x

数据不一致性:由于并发操作破坏了事务的隔离性

并发控制就是要用正确的调度并发操作,使一个用户事务的执行不受其他事务的干扰,从而避免造成数据的不一致性

对数据库的应用有时某些不一致性

并发控制主要技术:

  • 有封锁
  • 时间戳
  • 乐观控制法
  • 多版本并发控制

# 封锁

封锁:事务 T 在对某个数据对象 (例如表、记录等) 操作之前,先向系统发出请求,对其加锁

加锁后事务 T 就对该数据对象有了一定的控制,在事务 T 释放它的锁之前,其他的事物不能更新此数据对象

基本封锁类型:

  • 排他锁 (简记为 X 锁)
  • 共享锁 (简记为 S 锁)

排他锁又称为写锁

  • 若事务 T 对数据对象 A 加上 X 锁,则只允许 T 读取和修改 A,其他任何事务都不能再对 A 加任何类型的锁,直到 T 释放 A 上的锁
  • 保证其他事务在 T 释放 A 上的锁之前不能再读取和修改 A

共享锁又称为读锁

  • 若事务 T 对数据对象 A 加上 S 锁,则事务 T 可以读取但不能修改 A,其他事务只能再对 A 加 S 锁,而不能加 X 锁,直到 T 释放 A 上的 S 锁
  • 保证其他事物可以读 A,但在 T 释放 A 上的 S 锁之前不能对 A 做任何修改

# 封锁协议

封锁协议:在运用 X 锁和 S 锁对数据对象加锁时,需要约定一些规则,这些规则为封锁协议

  • 何时申请 X 锁或 S 锁
  • 持锁时间
  • 何时释放

对封锁方式规定不同的规则,就形成了各种不同的封锁协议,它们分别在不同的程度上为并发操作的正确调度提供一定的保证

保持数据一致性的常用封锁协议

三级封锁协议

  • 一级封锁协议
  • 二级封锁协议
  • 三级封锁协议

# 一级封锁协议:

事务 T 在修改数据 R 之前必须对其加 X 锁,直到事务结束才释放

  • 正常结束 (COMMIT)
  • 非正常结束 (ROLLBACK)

一级封锁协议可以防止丢失修改

在一级封锁协议中,如果仅仅是读数据不对其进行修改,是不需要加锁的,所以它不能保证可重复读和不读脏数据

# 二级封锁协议:

一级封锁协议加上事务 T 在读取数据 R 之前必须先对其加 S 锁,读完后即可释放 S 锁

二级封锁协议可以防止丢失修改和读脏数据

在二级封锁协议中,由于读完数据后即可释放 S 锁,所以它不可以保证可重复读

# 三级封锁协议:

一级封锁协议加上事务 T 在读取数据 R 之前必须先对其加 S 锁,直到事务结束后才释放

三级封锁协议可以防止丢失修改、读脏数据和不可重复读

# 三级协议的主要区别

什么操作需要申请封锁以及合适释放锁 (即持锁时间)

不同的封锁协议使事务达到一致性级别不同

  • 封锁协议级别越高,一致程度越高

# 隔离级别

在标准 SQL 规范中,定义了 4 个事务隔离级别。封锁协议和隔离级别并不是严格对应的。各种隔离级别所能避免的并发问题。

第一类更新丢失是指,由于某个事务的回滚操作,参与回滚的旧数据将其他事务的数据更新覆盖了

# 活锁与死锁

封锁技术可以有效地解决并发操作的一致性问题,但也带来一些新的问题

  • 死锁
  • 活锁

# 活锁

避免活锁:采用先来先服务的策略

  • 当多个事务请求封锁同一数据对象时
  • 按请求封锁的先后次序对这些事务排队
  • 该数据对象上的锁一旦释放,首先批准申请队列中第一个事务获得锁

# 死锁

解决死锁方法:

  • 预防死锁
  • 死锁的诊断与解除

死锁的预防:

  • 产生死锁的原因有两个或多个事务都已封锁了一些数据对象,然后又都请求对已为其他事务封锁的数据对象加锁,从而出现等待
  • 预防死锁的发生就是要破坏产生死锁的条件

预防死锁的方法:

  • 一次封锁法

    • 要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续运行
    • 存在的问题:
      • 降低系统并发度
      • 难于事先精确确定封锁对象
  • 顺序封锁法

    • 顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁

    • 顺序封锁法存在问题

      • 维护成本

        数据系统中封锁的数据对象极多,并且在不断地变化

      • 难以实现:很难事先确定每一个事务要封锁哪些对象

结论:

  • 在操作系统中广为采用的预防死锁的策略并不很适合数据库的特点
  • DBMS 在解决死锁的问题上更普遍采用的是诊断并解除死锁的方法

死锁诊断:

  • 超时法
    • 如果一个事务的等待时间超过了规定的时限,就认为发生了死锁
    • 优点:实现简单
    • 缺点:
      • 有可能误判死锁
      • 时限若设置得太长,死锁发生后不能及时发现
  • 事务等待图法
    • 用事务等待图动态反映所有事务的等待情况
      • 事务等待图是一个有向图G=(T,U)G=(T,U)
      • TT 是节点的集合,每个节点表示正运行的事务
      • UU 为边的集合,每条边表示事务等待的情况
      • T1T_1 等待T2T_2,则T1,T2T_1,T_2 之间划一条有向边,从T1T_1 指向T2T_2
    • 并发控制子系统周期性地 (比如每隔数秒) 生成事务等待图,检测事务。如果发现图中存在回路,则表示系统中出现了死锁。

处理死锁

  • 选择一个处理死锁代价最小的事务,使其撤销
  • 释放此事务持有的所有的锁,使其他事务能继续运行下去

# 并发调度的可串行性

可串行化调度:多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行地执行这些事务时的结果相同

可串行性:

  • 是并发事务正确调度的准则
  • 一个给定的并发调度,当且仅当它是可串行化的,才会认为是正确调度

冲突操作:

  • 指不同的事物对同一个事务的读写操作和写写操作
  • 其他操作是不冲突操作
  • 不同事务的冲突操作和同一事务的两个操作不能交换

可串行化的充分条件

  • 一个调度 Sc 在保证冲突操作的次序不变的情况下,通过交换两个事务不冲突操作的次序得到另一个调度 Sc',如果 Sc' 是串行的,称调度 Sc 为冲突可串行化调度
  • 一个调度是冲突可串行化,一定是可串行化的调度

冲突可串行化调度是可串行化的充分条件

# 两段锁封锁协议

封锁协议

  • 运用封锁方法时,对数据对象加锁时需要约定一些规则
    • 何时申请加锁
    • 持锁时间
    • 何时释放封锁等

两段锁协议是最常用的一种封锁协议,理论上证明使用两端封锁协议产生的是可串行化调度

两段锁协议:指所有事务必须分两个阶段对数据项加锁和解锁

  • 在对任何数据进行读、写操作前,事务首先要申请并获得对该数据的封锁
  • 在释放一个封锁之后,事务不再申请和获得任何其他封锁

"两段" 锁的含义:

所有事务必须分两个阶段对数据项加锁和解锁

  • 第一阶段是获得封锁,也称为扩展阶段
    • 事务可以申请获得任何数据项的任何类型的锁,但是不能释放任何锁
  • 第二阶段是释放封锁,也成为了收缩阶段
    • 事务可以释放任何数据项上的任何类型的锁,但是不能再申请任何锁

事务遵守两段锁协议是可串行化的充分条件,而不是必要条件

若并发事务都遵守两端锁协议,则对这些事务的任何并发调度策略都是可串行化的

若并发事务的一个调度是可串行化的,不一定所有事务都符合两段锁协议

两段锁协议与防止死锁的一次封锁法:

  • 一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就丌能继续执行,因此一次封锁法遵守两段锁协议
  • 但是两段锁协议并丌要求事务必须一次将所有要使用的数据全部加锁,因此遵守两段锁协议的事务可能发生死锁

# 锁定提示:

  • TABLOCK: 对表施加共享锁 S
  • HOLDLOCK: 与 TABLELOCK 一起使用,可将共享锁保留到事务完成,而不是读完数据立即释放
  • NOLOCK: 不施加任何锁,仅用于 select 语句
  • TABLOCKX: 对表施加排他锁 X
  • UPLOCK: 对表中指定元组施加更新锁,这时其他事务可以对同表中其他元素施加更新锁,但不能对表施加任何锁

例:对课程表施加共享锁,并且保持事务结束时再释放封锁:

SELECT * FROM Course with(TABLOCK HOLDLOCK)

# 并发控制实现途径

  • 修改应用程序,避免不必要的并发

  • 借助 DBMS 的功能

  • 利用开发工具的支持

  • 调整应用,借助应用程序和数据库结构实现并发

  • 大型关系系统都有比较好的并发控制功能

  • 只有在数据一致性要求特别特别严格,但并发度要求不高的时候可采用可重复读和可串行化级别

  • 在同一个事务当中,要适当根据需要来变更数据的锁定级别,但一般不要用 TABLELOCK 这样的粗粒度的封锁

  • 不同事务之间可以根据并发度的需要来设定隔离级别