系统架构设计笔记(9)——数据库事务管理

数据库系统运行的基本工作单位是事务,事务相当于操作系统中的进程,是用户定义的一个数据库操作序列,这些操作序列要么全做要么全不做,是一个不可分割的工作单位。事务具有以下特性:

(1)原子性( Atomicity):数据库的逻辑工作单位。
(2)一致性( Consistency):使数据库从一个一致性状态变到另一个一致性状态。
(3)隔离性( Isolation):不能被其他事务干扰。
(4)持续性(永久性)( Durability):一旦提交,改变就是永久性的。

事务通常以 BEGINTRANSACTION (事务开始)语句开始,以 COMMIT 或 ROLLBACK 语句结束。 COMMIT 称为 “ 事务提交语句 ” ,表示事务执行成功的结束。 ROLLBACK 称为 “ 事务回退语句 ” ,表示事务执行不成功的结束。从终端用户来看,事务是一个原子,是不可分割的操作序列。事务中包括的所有操作要么都做,要么都不做(就效果而言)。事务不应该丢失或被分割地完成。

1 并发控制

在多用户共享系统中,许多事务可能同时对同一数据进行操作,称为 “ 并发操作 ” ,此时数据库管理系统的并发控制子系统负责协调并发事务的执行,保证数据库的完整性不受破坏,同时避免用户得到不正确的数据。

1.1 数据库并发问题

数据库并发问题可以归结为 5 类,包括 3 类数据读问题(脏读 、 不可重复度 、 幻读)和 2 类数据更新问题(第一类丢失更新和第二类丢失更新)。

(1)脏读(dirty read)

A 事务读取了 B 事务尚未提交的更改数据,并在此数据的基础上进行操作 。 如果此时 B 事务回滚,那么 A 事务之前读到的数据就是脏数据。

时间序列 事务 A 事务 B
1 开始事务 开始事务
2 - 查询账户余额(100 元)
3 - 取出 50 元
4 查询账户余额(50 元)【脏读】 -
5 - 回滚事务(账户余额:100 元)
6 存入 100 元 -
7 提交事务(账户余额:150 元) -

这里因为发生脏读,导致账户损失了 50 元(事务 A 存款 100 元,事务 B 无影响,再加上原来的账户余额 100 元,最后的账户余额应该是 200 元才是)。

(2) 不可重复读(unrepeatable read)

不可重复读指的是事务在不同的时间点,读取到的数据不同。

时间序列 事务 A 事务 B
1 开始事务 开始事务
2 - 查询账户余额(100 元)
3 查询账户余额(100 元) -
4 - 取款 10 元
5 - 提交事务(账户余额:90 元)
6 查询账户余额(90 元) -

在时间序列 6,与在时间序列 3 时查询到的余额不同,发生不可重复读现象。

(3)幻读(phantom read)

幻象读一般发生在计算统计数据的事务中 。 A 事务读取了 B 事务提交的新增数据,这时 A 事务将出现幻象读的问题 。

假设在同一个事务中,两次统计名某银行支行所有账户的总金额,在两次统计过程中,刚好新增了一个存款账户 。那么,这两次统计的总金额肯定会不一致 。

时间序列 事务 A 事务 B
1 开始事务 开始事务
2 统计(总金额:10 w) -
3 - 新增存款账户(金额:1 w)
4 - 提交事务(总金额:11 w)
5 统计(总金额:11 w)幻读 -

(4)不可重复读与幻读比较

比较 不可重复读 幻读
读取对象 读到其它事务已经提交的修改或删除数据。 读到其它事务已经提交的新增数据。
采取措施 对所要操作的数据添加级锁,避免这些数据发生变化。 对所要操作的数据所在表添加级锁,即将整张表锁定(在 Oracle 中,是以多版本数据的方式实现的)。

(5)第一类丢失更新

A 事务回滚时,把 B 事务中已经提交的更新数据给覆盖咯 。

时间序列 事务 A 事务 B
1 开始事务 开始事务
2 查询账户余额(100 元) -
3 - 查询账户余额(100 元)
4 - 取款 10 元
5 - 提交事务(账户余额:90 元)
6 存入 10 元 -
7 回滚事务(账户余额:110 元) -

这个问题影响很大。这个例子中,账户余额应该还是 100 元(取款 10 元,存入 10 元,实际对账户无影响),但因为存在第一类丢失更新,导致银行损失 10 元。如果事务 A 先提交,那么账户将损失 10 元。

(6)第二类丢失更新

A 事务提交后覆盖了 B 事务已经提交的数据,导致 B 事务所做操作丢失。

时间序列 事务 A 事务 B
1 开始事务 开始事务
2 - 查询账户余额:100 元
3 查询账户余额:100 元 -
4 - 取款 10 元
5 - 提交事务(账户余额:90 元)
6 存款 10 元 -
7 提交事务(账户余额:110 元) -

上述示例,直接导致银行损失 10 元。如果 A 事务先提交,那么将导致账户损失 10 元。

1.2 封锁技术

1.2.1 锁类型

处理并发控制的主要方法是采用封锁技术。它有两种类型:排他型封锁( X 封锁)和共享型封锁( S 封锁),分别介绍如下:

  1. 排他型封锁(简称 X 封锁)。如果事务 T 对数据 A (可以是数据项 、 记录 、 数据集,乃至整个数据库)实现了 X 封锁,那么只允许事务 T 读取和修改数据 A ,其他事务要等事务 T 解除 X 封锁以后,才能对数据 A 实现任何类型的封锁。可见 X 封锁只允许一个事务独锁某个数据,具有排他性。

  1. 共享型封锁(简称 S 封锁) 。 X 封锁只允许一个事务独锁和使用数据,要求太严。需要适当放宽,例如可以允许并发读,但不允许修改,这就产生了 S 封锁概念。 S 封锁的含义是:如果事务 T 对数据 A 实现了 S 封锁,那么允许事务 T 读取数据 A ,但不能修改数据 A ,在所有 S 封锁解除之前绝不允许任何事务对数据 A 实现 X 封锁。

数据库是一个共享资源,它允许多个用户程序并行地存取数据库中的数据,但是,如果系统对并行操作不加以控制,就会存取不正确的数据,破坏数据库的完整性。

1.2.2 封锁协议

在多个事务并发执行的系统中,主要采取封锁协议来进行处理。

(1)一级封锁协议

事务 T 在修改数据 R 之前必须先对其加 X 锁,直到事务结束才释放。因为对数据修改加了排他性锁,所以可防止丢失修改,并保证事务 T 是可恢复的。但还是会发生不可重复读与脏读现象。

(2)二级封锁协议

二级封锁协议是在一级封锁协议的基础上,再加上事务 T 在读取数据 R 之前先对其加 S 锁,读完后即释放 S 锁。二级封锁协议不但可防止丢失修改,还因为加了共享读锁,因此可防止脏读现象,但仍然会发生不可重复读。

(3)三级封锁协议

一级封锁协议是在一级封锁协议的基础上,再加上事务 T 在读取数据 R 之前先对其加 S 锁,直到事务结束才释放。三级封锁协议可防止丢失修改 、 防止脏读与防止数据重复读。


以上锁协议,表 1 总结如下:

(4)两段锁协议

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

  1. 扩展阶段是在对任何数据进行读 、 写操作之前,首先要申请并获得对该数据的封锁;
  2. 收缩阶段是在释放一个封锁之后,事务不能再申请和获得任何其他封锁。若并发执行的所有事务均遵守两段封锁协议,则对这些事务的任何并发调度策略都是可串行化的。遵守两段封锁协议的事务可能发生死锁。

两段锁协议类似进程之间的 PV 信号量操作。

1.2.3 封锁粒度

所谓封锁的粒度即是被封锁数据目标的大小。在关系数据库中,封锁粒度有属性值 、 属性值集 、 元组 、 关系 、 某索引项(或整个索引) 、 整个关系数据库 、 物理页(块)等几种。

封锁粒度小则并发性高,但开销大;封锁粒度大则并发性低,但开销小。综合平衡照顾不同需求以合理选取适当的封锁粒度是很重要的。

1.2.4 死锁

采用封锁的方法固然可以有效防止数据的不一致性,但封锁本身也会产生一些麻烦,最主要的就是死锁问题。所谓死锁是指多个用户申请不同封锁,由于申请者均拥有一部分封锁权而又需等待另外用户拥有的部分封锁而引起的永无休止的等待。一般来讲,死锁是可以避免的,目前采用的办法有以下几种:

(1)预防法

此种方法是采用一定的操作方式以保证避免死锁的出现,顺序申请法 、 一次申请法等即是此类方法。

  1. 顺序申请法是指对封锁对象按序编号,在用户申请封锁时必须按编号顺序(从小到大或反之)申请,这样能避免死锁发生。
  2. 一次申请法即是指用户在一个完整操作过程中必须一次性申请它所需要的所有封锁,并在操作结束后一次性归还所有封锁,这样也能避免死锁的发生。

(2)解除法
此种方法允许产生死锁,并在死锁产生后通过解锁程序以解除死锁。这种方法需要有两个程序,一是死锁检测程序,用它测定死锁是否发生,另一是解锁程序,一旦经测定系统已产生死锁则启动解锁程序以解除死锁。

避免死锁的方法类似进程死锁避免方法,万物相通,本质相同。

2 数据库故障

数据库的故障可用事务的故障来表示,主要分为四类:

(1)事务故障

事务在运行过程中由于种种原因,如输入数据的错误 、 运算溢出 、 违反了某些完整性限制 、 某些应用程序的错误,以及并发事务发生死锁等,使事务未运行至正常终止点就被撤销,这种情况称为 “ 事务故障 ”。

(2)系统故障

系统故障是指系统在运行过程中,由于某种原因(如操作系统或数据库管理系统代码错误 、 操作员操作失误 、 特定类型的硬件错误(如 CPU 故障) 、 突然停电等造成系统停止运行),致使事务在执行过程中以非正常方式终止,这时内存中的信息丢失,但存储在外存储设备上的数据不会受影响。

(3)介质故障

系统在运行过程中,由于某种硬件故障,如磁盘损坏 、 磁头碰撞或由于操作系统的某种潜在的错误 、 瞬时强磁场干扰,使存储在外存上的数据部分损失或全部损失,称为 “ 介质故障 ”。 这类故障比前两类故障的可能性虽然小得多,但破坏性却最大。

(4)计算机病毒

计算机病毒是一种人为破坏计算机正常工作的特殊程序。通过读写染有病毒的计算机系统中的程序与数据,这些病毒可以迅速繁殖和传播,危害计算机系统和数据库。目前大多数病毒是在 PC 和其兼容机上传播的。有的病毒一侵入系统就马上摧毁系统,有的病毒有较长的潜伏期,有的病毒则只在特定的日期发生破坏作用,有的病毒感染系统所有的程序和数据,有的只影响特定的程序和数据。

3 恢复故障

要恢复,首先必须先有数据库日志文件。

3.1 事务日志

事务日志是针对数据库改变所做的记录,它可以记录针对数据库的任何操作,并将记录结果保存在独立的文件中。这种文件就称为日志文件。对于任何一个事务,事务日志都有非常全面的记录,根据这些记录可以将数据文件恢复成事务前的状态。从事务动作开始,事务日志就处于记录状态,事务执行过程中对数据库的任何操作都记录在内,直到用户提交或回滚后才结束记录。

日志文件是用来记录对数据库每一次更新活动的文件,在动态备份方式中,必须建立日志文件,后援副本和日志文件综合起来才能有效地恢复数据库;而在静态备份方式中,也可以建立日志文件,当数据库毁坏后可重新装入后援副本把数据库恢复到备份结东时刻的正确状态,然后利用日志文件,把已完成的事务进行重做处理,对故障发生时尚未完成的事务进行撤销处理。这样不必重新运行那些已完成的事务程序就可把数据库恢复到故障前某一时刻的正确状态。

例如,在热备份期间的某时刻 t1,系统把数据 A =100 备份到了磁带上,而在时刻 t2,某一事务对 A 进行了修改使 A=200。备份结束,后备副本上的 A 已是过时的数据了。为此,必须把备份期间各事务对数据库的修改活动登记下来,建立日志文件。这样,后备副本加上日志文件就能把数据库恢复到某一时刻的正确状态。

事务在运行过程中,系统把事务开始 、 事务结束 ( 包括 COMMIT 和ROLLB A CK),以及对数据库的插入 、 删除 、 修改等每一个操作作为一个登记记录会被存放到日志文件中。

每个记录包括的主要内容有 : 执行操作的事务标识 、 操作类型 、 更新前数据的旧值 ( 对插入操作而言此项为空值) 、 更新后的新值 ( 对删除操作而言此项为空值 )。 登记的次序严格按并行事务操作执行的时间次序,同时遵循 “ 先写日志文件 ” 的规则。写一个修改到数据库中和写一个表示这个修改的日志记录到日志文件中是两个不同的操作,有可能在这两个操作之间发生故障,即这两个写操作只完成了一个,如果先写了数据库修改,而在日志记录中没有登记这个修改,则以后就无法恢复这个修改了。因此,为了安全,应该先写日志文件,即首先把修改记录写到日志文件上,然后再写数据库的修改。这就是 “ 先写日志文件 ” 的原则。

备份还可以分为海量备份和增量备份。

  1. 海量备份是指每次备份全部数据库。
  2. 增量备份则指每次只备份上次备份后更新过的数据。如果数据库很大,事务处理又十分频繁,则增量备份是一种很有效的备份方式。

3.2 备份文件

备份 ( 转储 ) 是指 DBA 定期地将整个数据库复制到磁带或另一个磁盘上保存起来的过程。这些备用的数据文本称为后备副本 ( 后援副本 ) 。当数据库遭到破坏后就可以利用后备副本把数据库恢复,这时,数据库只能恢复到备份时的状态,从那以后的所有更新事务必须重新运行才能恢复到故障时的状态。

备份可分为静态备份 ( 冷备份 ) 和动态备份 ( 热备份 ) 。

  1. 静态备份是指备份期间不允许 ( 或不存在 ) 对数据库进行任何存取 、 修改活动。静态备份简单,但备份必须等待用户事务结束才能进行,同样,新的事务必须等待备份结束才能执行。显然,这会降低数据库的可用性;
  2. 动态备份是指备份期间允许对数据库进行存取或修改,即备份和用户事务可以并发执行。动态备份可克服静态备份的缺点,但是,备份结束时后援副本上的数据并不能保证正确有效。

3.3 恢复策略

在数据库系统中,恢复的基本含义就是恢复数据库本身。也就是说,在发生某种故障使数据库当前的状态已经不再正确时,把数据库恢复到已知为正确的某一状态。目前数据库系统中最常用的恢复方法是转储和登记日志文件,可根据故障的不同类型,采用不同的恢复策略。

(1)恢复事务故障

事务故障是指事务未运行至正常终止点前被撤销,这时恢复子系统应对此事务做撤销处理。事务故障的恢复是由系统自动完成的,不需要用户干预,步骤如下:

  1. 反向扫描文件日志,查找该事务的更新操作。
  2. 对该事务的更新操作执行逆操作(因为是撤销事务,所以为逆向操作)。
  3. 继续反向扫描日志文件,查找该事务的其他更新操作,并做同样处理。
  4. 如此处理下去,直至读到此事务的开始标记,事务故障恢复完成。

(2)恢复系统故障

系统故障发生时,造成数据库不一致状态的原因有两个:一是由于一些未完成事务对数据库的更新已写入数据库;二是由于一些已提交事务对数据库的更新还留在缓冲区没来得及写入数据库。系统故障的恢复是在重新启动时自动完成的,不需要用户干预,步骤如下:

  1. 正向扫描日志文件,找出在故障发生前已经提交的事务,将其事务标识记入重做( Redo )队列。同时找出故障发生时尚未完成的事务,将其事务标识记入撤销( Undo )队列。
  2. 对撤销队列中的各个事务进行撤销处理:反向扫描日志文件,对每个 Undo 事务的更新操作执行逆操作。
  3. 对重做队列中的各个事务进行重做处理:正向扫描日志文件,对每个 Redo 事务重新执行日志文件登记的操作。

(3)恢复介质故障与病毒破坏

在发生介质故障和遭病毒破坏时,磁盘上的物理数据库被破坏,这时的恢复操作可分为三步:

  1. 装入最新的数据库后备副本(突显备份的重要性),使数据库恢复到最近一次转储时的一致性状态。
  2. 从故障点开始反向读日志文件,找出已提交事务标识将其记入重做队列。
  3. 从起始点开始正向读日志文件,根据重做队列中的记录,重做所有已完成事务,将数据库恢复至故障前某一时刻的一致状态。

(4)具有检查点的恢复技术

检查点记录的内容可包括:

  1. 建立检查点时刻所有正在执行的事务清单。
  2. 这些事务最近一个日志记录的地址。

采用检查点的恢复步骤如下:

  1. 从重新开始文件中找到最后一个检查点记录在日志文件中的地址,由该地址在日志文件中找到最后一个检查点记录。
  2. 由该检查点记录得到检查点建立时所有正在执行的事务清单队列(A)。
  3. 建立重做队列(R)和撤销队列(U),把 A 队列放入 U 队列中, R 队列为空。
  4. 从检查点开始正向扫描日志文件,若有新开始的事务 T1,则把 T1 放入 U 队列,若有提交的事务 T2,则把 T2 从 U 队列移到 R 队列,直至日志文件结束。
  5. 对 U 队列的每个事务执行 Undo 操作,对 R 队列的每个事务执行 Redo 操作。

重做已完成的事务并撤销正在进行的事务。

DBA 要做的基本操作是:

  1. 重装最近转储的后援副本。
  2. 运行日志文件,执行系统提供的恢复命令。

数据库安全和恢复是数据库系统正常运行的保证。大型数据库管理系统一般都提供了实现安全机制的保证,即由系统提供了相应的功能,但小型的数据库管理系统并非都具有相应功能,因此有时需要人工的辅助措施,用以保证数据库的安全和恢复。

发布了671 篇原创文章 · 获赞 766 · 访问量 99万+

猜你喜欢

转载自blog.csdn.net/deniro_li/article/details/105031962