第5章 数据库安全保护
本章概要 随着计算机的普及,数据库的使用也越来越广泛。 在前面我们已经讲到,数据库系统中的数据是由DBMS统一进行管理和控制的。 例如,一个企业的全部信息,国家机构的事务管理信息,国防情报等机密信息,都集中地存放在数据库中。 在前面我们已经讲到,数据库系统中的数据是由DBMS统一进行管理和控制的。 为了适应和满足数据共享的环境和要求,DBMS要保证整个系统的正常运转,防止数据意外丢失和不一致数据的产生,以及当数据库遭受破坏后能迅速地恢复正常,这就是数据库的安全保护。 DBMS对数据库的安全保护功能是通过四方面实现的,即安全性控制、完整性控制、并发性控制和数据库恢复。 本章就是从这四方面来介绍数据库的安全保护功能: 要求掌握安全性、完整性、并发性和数据库恢复的含义, 掌握这四方面分别实现安全保护功能的方法。
5.1 数据库的安全性 5.1.1 数据库安全性的含义 数据库的安全性是指保护数据库,以防止非法使用所造成数据的泄露、更改或破坏。 5.1 数据库的安全性 5.1.1 数据库安全性的含义 数据库的安全性是指保护数据库,以防止非法使用所造成数据的泄露、更改或破坏。 安全性问题有许多方面,其中包括: (1)法律、社会和伦理方面,例如请求查询信息的人是不有合法的权力。 (2)物理控制方面,例如计算机机房或终端是否应该加锁或用其他方法加以保护。 (3)政策方面,确定存取原则,允许哪些用户存取哪些数据。 (4)运行方面,使用口令时,如何使口令保持秘密。 (5)硬件控制方面,CPU是否提供任何安全性方面的功能,诸如存储保护键或特权工作方式。
5.1.2 安全性控制的一般方法 安全性控制是指要尽可能地杜绝所有可能的数据库非法访问。 (6)操作系统安全性方面,在主存储器和数据文件用过以后,操作系统是否把它们的内容清除掉。(7)数据库系统本身安全性方面。这里讨论的是数据库本身的安全性问题,即主要考虑安全保护的策略,尤其是控制访问的策略。 5.1.2 安全性控制的一般方法 安全性控制是指要尽可能地杜绝所有可能的数据库非法访问。 用户非法使用数据库可以有很多种情况。例如,编写合法的程序绕过DBMS授权机制,通过操作系统直接存取、修改或备份有关数据。 用户访问非法数据,无论它们是有意的还是无意的,都应该加以严格控制,因此,系统还要考虑数据信息的流动问题并加以控制,否则有隐蔽的危险性。
因为数据的流动可能使无权访问的用户获得访问权利。 例如,甲用户可以访问文件F1,但无权访问文件F2,如果乙用户把文件F2移至文件F1中之后,则由于乙用户的操作,使甲用户获得了对文件F2的访问权。此外,用户可以多次利用允许的访问结果,经过逻辑推理得到他无权访问的数据。 为防止这一点,访问的许可权还要结合过去访问的情况而定。 可见安全性的实施是要花费一定代价的,安全保护策略就是要以最小的代价来防止对数据的非法访问,层层设置安全措施。
实际上,安全性问题并不是数据库系统所独有的,所有计算机系统中都存在这个问题。 在计算机系统中,安全措施是一级一级层层设置的,安全控制模型如图5.1所示。 图5.1 安全控制模型 用户标识和鉴定 用户 用户存取权限控制 DBMSMMS 操作系统安全保护 OS 密码存储 DB
根据图5.1的安全模型,当用户进入计算机系统时,系统首先根据输入的用户标识进行身份的鉴定,只有合法的用户才准许进入系统。 对已进入系统的用户,DBMS还要进行存取控制,只允许用户进行合法的操作。 DBMS是建立在操作系统之上的,安全的操作系统是数据库安全的前提。 操作系统应能保证数据库中的数据必须由DBMS访问,而不允许用户越过DBMS,直接通过操作系统访问。 数据最后可以通过密码的形式存储到数据库中。
5.1.2.1 用户标识和鉴定 (Identification and Authentication) 数据库系统是不允许一个未经授权的用户对数据库进行操作的。 用户标识和鉴定是系统提供的最外层的安全保护措施,其方法是由系统提供一定的方式让用户标识自己的名字或身份,系统内部矛盾记录着所有合法用户的标识,每次用户要求进入系统时,由系统进行核实,通过鉴定后才提供机器的使用权。 用户标识和鉴定的方法有多种,为了获得更强的安全性,往往是多种方法并举,常用的方法有以下几种:
1.用一个用户名或用户标识符来标明用户的身份,系统以此来鉴别用户的合法性。如果正确,则可进入下一步的核实,否则,不能使用计算机。 2.用户标识符是用户公开的标识,它不足以成为鉴别用户身份的凭证。为了进一步核实用户身份,常采用用户名与口令(Password)相结合的方法,系统通过核对口令判别用户身份的真伪。 系统有一张用户口令表,为每个用户保持一个记录,包括用户名和口令两部分数据。 用户先输入用户名,然后系统要求用户输入口令。 为了保密,用户在终端上输入的口令不显示在屏幕上。 系统核对口令以鉴别用户身份。 3.通过用户名和口令来鉴定用户的方法简单易行,但该方法在使用时,由于用户名和口令的产生和使用比较简单,也容易被窃取,因此还可采用更复杂的方法。
例如,每个用户都预先约定好一个过程或者函数,鉴别用户身份时,系统提供一个随机数,用户根据自己预先约定的计算过程或者函数进行计算,系统根据计算结果辨别用户身份的合法性。 例如,让用户记住一个表达式,如T=X+2Y,系统告诉用户X=1,Y=2,如果用户回答T=5,则证实了该用户的身份。 当然,这是一个简单的例子,在实际使用中,还可以设计复杂的表达式,以使安全性更好。系统每次提供不同的X,Y值,其他人可能看到的是X、Y的值,但不能推算出确切的变换公式T。
5.1.2.2 用户存取权限控制 用户存取权限指的是不同的用户对于不同的数据对象允许执行的操作权限。 在数据库系统中,每个用户只能访问他有权存取的数据并执行有权使用的操作。 因此,必须预先定义用户的存取权限。 对于合法的用户,系统根据其存取权限的定义对其各种操作请求进行控制,确保合法操作。 存取权限由两个要素组成,数据对象和操作类型。 定义一个用户的存取权限就是要定义这个用户可以在哪些数据对象上进行哪些类型的操作。
在数据库系统中,定义用户存取权限称为授权(Authorization)。 第三章讨论SQL的数据控制功能时,我们已知道授权有两种:系统特权和对象特权。 系统特权是由DBA授予某些数据库用户,只有得到系统特权,才能成为数据库用户。 对象特权可以由DBA授予,也可以由数据对象的创建者授予,使数据库用户具有对某些数据对象进行某些操作的特权。 在系统初始化时,系统中至少有一个具有DBA特权的用户,DBA可以通过GRANT语句将系统特权或对象特权授予其他用户。 对于已授权的用户可以通过REVOKE语句收回所授予的特权。
这些授权定义经过编译后以一张授权表的形式存放在数据字典中。 授权表主要有三个属性,用户标识、数据对象和操作类型。 用户标识不但可以是用户个人,也可以是团体、程序和终端。在非关系系统中,存取控制的数据对象仅限于数据本身。 而关系系统中,存取控制的数据对象不仅有基本表、属性列等数据本身,还有内模式、外模式、模式等数据字典中的内容。 下表列出了关系系统中的存取权限。 数据对象 操作类型 模式 建立、修改、检索 外模式 内模式 数据 表 查找、插入、修改、删除 属性列
对于授权表,一个衡量授权机制的重要指标就是授权粒度,即可以定义的数据对象的范围; 在关系数据库中,授权粒度包括关系、记录或属性。 一般来说,授权定义中粒度越细,授权子系统就越灵活 例如,表5.2是一个授权粒度很粗的表,它只能对整个关系授权。 如USER1拥有对关系S的一切权限; USER2拥有对关系C的SELECT权和对关系SC的UPDATE权; USER3只可以向关系SC中插入新记录。
表5.2 授权表1 用户标识 数据对象 操作类型 USER1 关系S ALL USER2 关系C SELECT 关系SC UPDATE INSERT … 表5.2 授权表1
表5.3是一个授权粒度较为精细,它可以精确到关系的某一属性。 USER1拥有对关系S的一切权限; USER2只能查询关系C的CNO属性和修改关系SC的SCORE属性; USER3可以向关系SC中插入新记录。 表5.3 授权表2 用户标识 数据对象 操作类型 USER1 关系S ALL USER2 列C.CNO SELECT 列SC.SCORE UPDATE USER3 关系SC INSERT …
表5.3的授权表中的授权只涉及到数据名(关系、记录或属性的名字),而未提到具体的值。 系统不必访问具体的数据本身,就可以执行这种控制。这种控制称为“值独立”的控制。 表5.4中的授权表则不但可以对属性列授权,还可以提供与数值有关的授权,即可以对关系中的一组记录授权。 比如,USER1只能对计算机系的学生进行操作。 对于提供与数据值有关的授权,系统必须能够支持存取谓词的操作。 表5.4 授权表3 用户标识 数据对象 操作类型 存取谓词 USER1 关系S ALL DEPT=‘计算机’ USER2 列C.CNO SELECT 列SC.SCORE UPDATE USER3 关系SC INSERT …
可见授权粒度越细,授权子系统就越灵活,能够提供的安全性就越完善。 但另一方面,如果用户比较多,数据库比较大,授权表将很大,而且每次数据库访问都要用到这张表做授权检查,这将影响数据库的性能。 所幸的是,在大部分数据库中,需要保密的数据是少数,对于大部分公开的数据,可以一次性地授权给PUBLIC,而不必再对每个用户个别授权。 对于表5.4中与数据值有关的授权,可以通过另外一种数据库安全措施,即定义视图。
5.1.2.3 定义视图 为不同的用户定义不同的视图,可以限制各个用户的访问范围。 5.1.2.3 定义视图 为不同的用户定义不同的视图,可以限制各个用户的访问范围。 通过视图机制把要保密的数据对无权存取这些数据的用户隐藏起来,从而自动地对数据提供一定程度的安全保护。例如,USER1只能对计算机系的学生进行操作, 一种方法是通过授权机制对USER1授权,如表5.5所示, 另一种简单的方法就是定义一个计算机系的视图。 但视图机制的安全保护功能太不精细,往往不能达到应用系统的要求,其主要功能在于提供了数据库的逻辑独立性。 在实际应用中,通常将视图机制与授权机制结合起来使用,首先用视图机制屏蔽一部分保密数据,然后在视图上面再进一步定义存取权限。
5.1.2.4 数据加密(Data Encryption) 前面介绍的几种数据库安全措施,都是防止从数据库系统窃取保密数据,不能防止通过不正常渠道非法访问数据,例如,偷取存储数据的磁盘,或在通信线路上窃取数据,为了防止这些窃密活动,比较好的办法是对数据加密。 数据加密是防止数据库中数据在存储和传输中失密的有效手段。 加密的基本思想是根据一定的算法将原始数据(术语为明文,Plain text)加密成为不可直接识别的格式(术语为密文,Clipher text),数据以密码的形式存储和传输。
通常将这两种方法结合起来使用,就可以达到相当高的安全程度。 加密方法有两种, 一种是替换方法,该方法使用密钥(Encryption Key)将明文中的每一个字符转换为密文中的一个字符。 另一种是转换方法,该方法将明文中的字符按不同的顺序重新排列。 通常将这两种方法结合起来使用,就可以达到相当高的安全程度。 例如美国1977年制定的官方加密标准,数据加密标准(Data Encryption Standard,简称DES)就是使用这种算法的例子。 数据加密后,对于不知道解密算法的人,即使利用系统安全措施的漏洞非法访问数据,也只能看到一些无法辨认的二进制代码。 合法的用户检索数据时,首先提供密码钥匙,由系统进行译码后,才能得到可识别的数据。
目前不少数据库产品提供了数据加密例行程序,用户可根据要求自行进行加密处理,还有一些未提供加密程序的产品也提供了相应的接口,通话用户用其他厂商的加密程序对数据加密。 用密码存储数据,在存入时需加密,在查询时需解密,这个过程会占用大量系统资源,降低了数据库的性能。 因此数据加密功能通常允许用户自由选择,只对那些保密要求特别高的数据,才值得采用此方法。
5.1.2.5 审计(Audit) 前面介绍的各种数据库安全性措施,都可将用户操作限制在规定的安全范围内。 但实际上任何系统的安全性措施都不是绝对可靠的,窃密者总有办法打破这些控制。 对于某些高度敏感的保密数据,必须以审计功作为预防手段。 审计功能是一种监视措施,跟踪记录有关数据的访问活动。 审计追踪把用户对数据库的所有操作自动记录下来,存放在一个特殊文件上中,即审计日志(Audit Log)中。 记录的内容一般包括: 操作类型,如修改、查询等; 操作终端标识与操作者标识; 操作日期和时间;
使用审计功能会大大增加系统的开销,所以DBMS通常将其作为可选特征,提供相应的操作语句可灵活地打开或关闭审计功能。 操作所涉及到的相关数据,如基本表、视图、记录、属性等;数据的前象和后象。利用这些信息,可以重现导致数据库现有状况的一系列事件,以进一步找出非法存取数据的人、时间和内容等。 使用审计功能会大大增加系统的开销,所以DBMS通常将其作为可选特征,提供相应的操作语句可灵活地打开或关闭审计功能。 例如,可使用如下SQL语句打开对表S的审计功能,对表S的每次成功的查询、增加、删除、修改操作都作审计追踪: AUDIT SELECT,INSERT,DELETE,UPDATE, ON S WHENEVER SUCCESSFUL 要关闭对表S的审计功能可以使用如下语句: NO AUDIT ALL ON S。
5.2 完整性控制 5.2.1 数据库完整性的含义 数据库的完整性是指保护数据库中数据的正确性、有效性和相容性,防止错误的数据进入数据库造成无效操作。 有关完整性的含义在第一章中已作简要介绍。 比如年龄属于数值型数据,只能含0,1,…9,不能含字母或特殊符号; 月份只能用1~12之间的正整数表示; 表示同一事实的两个数据应相同,否则就不相容,如一个人不能有两个学号。 显然,维护数据库的完整性非常重要,数据库中的数据是否具备完整性关系到数据能否真实地反映现实世界。
数据库的完整性和安全性是数据库保护的两个不同的方面。 安全性是保护数据库,以防止非法使用所造成数据的泄露、更改或破坏,安全性措施的防范对象是非法用户和非法操作; 完整性是防止合法用户使用数据库时向数据库中加入不符合语义的数据,完整性措施的防范对象是不合语义的数据。 但从数据库的安全保护角度来讲,安全性和完整性又是密切相关的。
5.2.2 完整性规则的组成 为了实现完整性控制,数据库管理员应向DBMS提出一组完整性规则,来检查数据库中的数据,看其是否满足语义约束。 5.2.2 完整性规则的组成 为了实现完整性控制,数据库管理员应向DBMS提出一组完整性规则,来检查数据库中的数据,看其是否满足语义约束。 这些语义约束构成了数据库的完整性规则,这组规则作为DBMS控制数据完整性的依据。 它定义了何时检查、检查什么、查出错误又怎样处理等事项。 具体地说,完整性规则主要由以下三部分构成: 1. 触发条件:规定系统什么时候使用规则检查数据; 2. 约束条件:规定系统检查用户发出的操作请求违背了什么样的完整性约束条件; 3. 违约响应:规定系统如果发现用户的操作请求违背了完整性约束条件,应该采取一定的动作来保证数据的完整性,即违约时要做的事情。
某一条语句执行完成后,系统立即对此数据进行完整性约束条件检查。 完整性规则从执行时间上可分为立即执行约束(Immediate Constraints)和延迟执行约束(Deferred Constrainsts)。 立即执行约束是指在执行用户事务过程中,某一条语句执行完成后,系统立即对此数据进行完整性约束条件检查。 延迟执行约束是指在整个事务执行结束后,再对约束条件进行完整性检查,结果正确后才能提交。 某一条语句执行完成后,系统立即对此数据进行完整性约束条件检查。
如果发现用户操作请求违背了立即执行约束,则可以拒绝该操作,以保护数据的完整性。 例如,银行数据库中“借贷总金额应平衡”的约束就应该属于延迟执行约束,从账号A转一笔钱到账号B为一个事务,从账号A转出去钱后,账就不平了,必须等转入账号B后,账才能重新平衡,这时才能进行完整性检查。 如果发现用户操作请求违背了立即执行约束,则可以拒绝该操作,以保护数据的完整性。 如果发现用户操作请求违背了延迟执行约束,而又不知道是哪个事务的操作破坏了完整性,则只能拒绝整个事务,把数据库恢复到该事务执行前的状态。 一条完整性规则可以用一个五元组(D,O,A,C,P)来形式化地表示。其中: D(data):代表约束作用的数据对象; O(operation):代表触发完整性检查的数据库操作,即当用户发出什么操作请求时需要检查该完整性规则;
例如,对于“学号不能为空”的这条完整性约束, A(assertion):代表数据对象必须满足的语义约束,这是规则的主体; C(condition):代表选择A作用的数据对象值的谓词; P(procdure):代表违反完整性规则时触发执行的操作过程。 例如,对于“学号不能为空”的这条完整性约束, D:代表约束作用的数据对象为SNO属性; O(operation):当用户插入或修改数据时需要检查该完整性规则; A(assertion):SNO不能为空; C(condition):A可作用于所有记录的SNO属性; P(procdure):拒绝执行用户请求。
关系模型的完整性包括实体完整性,参照完整性和用户定义完整性。 对于违反实体完整性和用户定义完整性规则的操作一般都是采用拒绝执行的方式进行处理。 而对于违反参照完整性的操作,并不都是简单的拒绝执行,一般在接受这个操作的同时,执行一些附加的操作,以保证数据库的状态仍然是正确的。 例如在删除被参照关系中的元组时,应该将参照关系中所有的外码值与被参照关系中要删除元组主码值相对应的元组一起删除。 比如,要删除S关系中SNO=‘S1’的元组,而SC关系中又有两个SNO=‘S1’的元组。 这时根据应用环境的语义,因为当一个学生毕业或退学后,他的个人记录从S关系中删除,选课记录也应随之从SC表中删除,所以应该将SC关系中两个SNO=‘S1’的元组一起删除。
这些完整性规则都由DBMS提供的语句进行描述,经过编译后存放在数据字典中。 一旦进入系统,就开始执行该组规则。 其主要优点是违约由系统来处理,而不是由用户处理。 另外,规则集中在数据字典中,而不是散布在各应用程序之中,易于从整体上理解和修改,效率较高。 数据库系统的整个完整性控制都是围绕着完整性约束条件进行的,从这个角度来看,完整性约束条件是完整性控制机制的核心。
5.2.3 完整性约束条件的分类 1.值的约束:即对数据类型、数据格式、取值范围等进行规定。 5.2.3 完整性约束条件的分类 5.2.3.1 从约束条件使用的对象分为值的约束和结构的约束 1.值的约束:即对数据类型、数据格式、取值范围等进行规定。 (1)对数据类型的约束,包括数据的类型、长度、单位和精度等。 例如,规定学生姓名的数据类型应为字符型,长度为8。 (2)对数据格式的约束。 例如,规定出生日期的数据格式为YY.MM.DD。 (3)对取值范围的约束。 例如,月份的取值范围为1~12,日期1~31。 (4)对空值的约束。空值表示未定义或未知的值,它与零值和空格不同。有的列值允许空值,有的则不允许。 例如,学号和课程号不可以为空值,但成绩可以为空值。
数据库中同一关系的不同属性之间,应满足一定的约束条件,同时,不同关系的属性之间也有联系,也应满足一定的约束条件。 2.结构约束:即对数据之间联系的约束。 数据库中同一关系的不同属性之间,应满足一定的约束条件,同时,不同关系的属性之间也有联系,也应满足一定的约束条件。 常见的结构约束有如下四种: (1)函数依赖约束:说明了同一关系中不同属性之间应满足的约束条件。 如:2NF,3NF,BCNF这些不同的范式应满足不同的约束条件。 大部分函数依赖约束都是隐含在关系模式结构中的,特别是对于规范化程度较高的关系模式,都是由模式来保持函数依赖的。
其中,实体完整性约束和参照完整性约束是关系模型的两个极其重要的约束,被称为关系的两个不变性。统计约束实现起来开销很大。 (2)实体完整性约束:说明了关系键的属性列必须唯一,其值不能为空或部分为空。 (3)参照完整性约束:说明了不同关系的属性之间的约束条件,即外部键的值应能够在参照关系的主键值中找到或取空值。 (4)统计约束,规定某个属性值与一个关系多个元组的统计值之间必须满足某种约束条件。 例如,规定系主任的奖金不得高于该系的平均奖金的40%。,不得低于该系的平均奖金的20%。这里该系平均奖金的值就是一个统计计算值。 其中,实体完整性约束和参照完整性约束是关系模型的两个极其重要的约束,被称为关系的两个不变性。统计约束实现起来开销很大。
1.静态约束 2.动态约束 5.2.3.2 从约束对象的状态分为静态约束和动态约束 5.2.3.2 从约束对象的状态分为静态约束和动态约束 1.静态约束 静态约束是指对数据库每一个确定状态所应满足的约束条件,是反映数据库状态合理性的约束,这是最重要的一类完整性约束。 上面介绍的值的约束和结构的约束均属于静态约束。 2.动态约束 动态约束是指数据库从一种状态转变为另一种状态时,新旧值之间所应满足的约束条件,动态约束反映的是数据库状态变迁的约束。 例如,学生年龄在更改时只能增长,职工工资在调整时不得低于其原来的工资。
5.3 并发控制与封锁 5.3.1 数据库并发性的含义 上一节讨论的完整性是保证各个事务本身能得到正确的数据,只考虑一个用户使用数据库的情况,但实际上数据库中有许多用户。 每个用户在存取数据库中的数据时,可能是串行执行,即每个时刻只有一个用户程序运行,也可能是多个用户并行地存取数据库。 数据库的最大特点之一就是数据资源是共享的,串行执行意味着一个用户在运行程序时,其他用户程序必须等到这个用户程序结束才能对数据库进行存取,这样如果一个用户程序涉及大量数据的输入/输出交换,则数据库系统的大部分时间将处于闲置状态。
因此,为了充分利用数据库资源,很多时候数据库用户都是对数据库系统并行存取数据,这样就会发生多个用户并发存取同一数据块的情况,如果对并发操作不加控制可能会产生不正确的数据,破坏数据的完整性,并发控制就是解决这类问题,以保持数据库中数据的一致性,即在任何一个时刻数据库都将以相同的形式给用户提供数据。 5.3.2 事务(Transaction) 5.3.2.1 事务的定义 在上一节中就曾提到过事务的概念,DBMS的并发控制也是以事务为基本单位进行的。 那么到底什么是事务呢?
事务是数据库系统中执行的一个工作单位,它是由用户定义的一组操作序列。 一个事务可以是一组SQL语句、一条SQL语句或整个程序,一个应用程序可以包括多个事务。 事务的开始与结束可以由用户显式控制。如果用户没有显式地定义事务,则由DBMS按照缺省规定自动划分事务。在SQL语言中,定义事务的语句有三条: BEGIN TRANSACTION COMMIT ROLLBACK BEGIN TRANSACTION表示事务的开始; COMMIT表示事务的提交,即将事务中所有对数据库的更新写回到磁盘上的物理数据库中去,此时事务正常结束; ROLLBACK表示事务的回滚,即在事务运行的过程中发生了某种故障,事务不能继续执行,系统将事务中对数据库的所有已完成的更新操作全部撤销,再回滚到事务开始时的状态。
5.3.2.2 事务的特征 事务是由有限的数据库操作序列组成,但并不是任意的数据库操作序列都能成为事务,为了保护数据的完整性,一般要求事务具有以下四个特征: 1.原子性(Atomic) 一个事务是一个不可分割的工作单位,事务在执行时,应该遵守“要么不做,要么全做”(nothing or all)的原则,即不允许事务部分的完成。 即使因为故障而使事务未能完成,它执行的部分结果要被取消。
事务对数据库的作用是数据库从一个一致状态转变到另一个一致状态。 所谓数据库的一致状态是指数据库中的数据满足完整性约束。 2.一致性(Consistency) 事务对数据库的作用是数据库从一个一致状态转变到另一个一致状态。 所谓数据库的一致状态是指数据库中的数据满足完整性约束。 例如,银行企业中,“从帐号A转移资金额R到帐号B”是一个典型的事务,这个事务包括两个操作,从帐号A中减去资金额R和在帐号B中增加资金额R,如果只执行其中一个操作,则数据库处于不一致状态,帐务会出现问题。也就是说,两个操作要么全做,要么全不做,否则就不能成为事务。 可见事务的一致性与原子性是密切相关的。
3.隔离性(Isolation) 如果多个事务并发地执行,应像各个事务独立执行一样,一个事务的执行不能被其他事务干扰。 即一个事务内部的操作及使用的数据对并发的其他事务是隔离的。 并以控制就是为了保证事务间的隔离性。 4.持久性(Durability) 指一个事务一旦提交,它对数据库中数据的改变就应该是持久的,即使数据库因故障而受到破坏,DBMS也应该能够恢复。
事务上述四个性质的英文术语的第一个字母为ACID。因此,这四个性质以称为事务的ACID准则。 下面是一个事务的例子,从帐号A转移资金额R到帐号B: BEGIN TRANSACTION READ A A←A-R IF A<0 /* A 款不足*/ THEN BEGIN DISPLAY “A款不足” ROLLBACK END ELSE /* 拨款 */ B←B+R DISPLAY “拨款完成” COMMIT
这是对一个简单事务的完整的描述。 该事务有两个出口: 当A 帐号的款项不足时,事务以ROLLBACK(撤销)命令结束,即撤销该事务的影响; 另一个出口是以COMMIT(提交)命令结束,完成从帐号A到帐号B的拨款。 在COMMIT之前,即在数据库修改过程中,数据可能是不一致的,事务本身也可能被撤销。 只有在COMMIT之后,事务对数据库所产生的变化才对其他事务开放,这就可以避免其他事务访问不一致或不存在的数据。
5.3.3 并发操作与数据的不一致性 当同一数据库系统中有多个事务并发运行时,如果不加以适当控制,可能产生数据的不一致性。 5.3.3 并发操作与数据的不一致性 当同一数据库系统中有多个事务并发运行时,如果不加以适当控制,可能产生数据的不一致性。 [例1]并发取款操作。假设存款余额R=1000元,甲事务T1取走存款100元,乙事务T2取走存款200元,如果正常操作,即甲事务T1执行完毕再执行乙事务T2,存款余额更新后应该是700元。但是如果按照如下顺序操作,则会有不同的结果: 甲事务T1读取存款余额R =1000元; 乙事务T2读取存款余额R =1000元; 甲事务T1取走存款100元,修改存款余额R =R -100=900,把R =900写回到数据库; 乙事务T2取走存款200元,修改存款余额R =R -200=800,把R =800写回到数据库。
结果两个事务共取走存款300元,而数据库中的存款却只少了200元。 得到这种错误的结果是由甲乙两个事务并发操作引起的,数据库的并发操作导致的数据库不一致性主要有以下三种: 1.丢失更新(Lost Update) 当两个事务T1和T2读入同一数据做修改,并发执行时, T2把T1或T1把T2的修改结果覆盖掉,
造成了数据的丢失更新问题,导致数据的不一致。 仍以例6.1中的操作为例进行分析。 在表5.5中,数据库中R的初值是1000,事务T1包含三个操作:读入R初值(FIND R);计算(R=R-100);更新R(UPDATE R)。 事务T2也包含三个操作:FIND R;计算(R=R-200);UPDATE R。 如果事务T1和T2顺序执行,则更新后,R的值是700。但如果T1和T2按照表5.5所示的并发执行,R的值是800,得到错误的结果,原因在于在t7时刻丢失了T1对数据库的更新操作。 因此,这个并发操作不正确。
表5.5 丢失更新问题 时间 事务T1 数据库中R的值 事务T2 T0 1000 t1 FIND R t2 t3 R=R-100 t4 1000 t1 FIND R t2 t3 R=R-100 t4 R=R-200 t5 UPDATE R t6 900 t7 800 表5.5 丢失更新问题
原因在于在t4时刻事务T2读取了T1未提交的更新操作结果,这种值是不稳定的,在事务T1结束前随时可能执行ROLLBACK操作。 2.污读(Dirty Read) 事务T1更新了数据R,事务T2读取了更新后的数据R,事务T1由于某种原因被撤消,修改无效,数据R恢复原值。事务T2得到的数据与数据库的内容不一致,这种情况称为“污读”。 在表5.6中,事务T1把R的值改为900,但此时尚未做COMMIT操作,事务T2将修改过的值900读出来,之后事务T1执行ROLLBACK操作,R的值恢复为1000,而事务T2将仍在使用已被撤消了的R值900。 原因在于在t4时刻事务T2读取了T1未提交的更新操作结果,这种值是不稳定的,在事务T1结束前随时可能执行ROLLBACK操作。 对于这些未提交的随后又被撤消的更新数据称为“脏数据”。 比如,这里事务T2在t2时刻读取的就是“脏数据”。
表5.6 污读问题 时间 事务T1 数据库中R的值 事务T2 t0 1000 t1 FIND R t2 R=R-100 t3 1000 t1 FIND R t2 R=R-100 t3 UPDATE R t4 900 t5 ROLLBACK t6 表5.6 污读问题
3.不可重读(Unrepeatable Read) 事务T1读取了数据R,事务T2读取并更新了数据R,当事务T1再读取数据R以进行核对时,得到的两次读取值不一致,这种情况称为“不可重读”。 在表5.7中,在t0时刻事务T1读取R的值为1000,但事务T2在t4时刻将R的值更新为为800。所以T1所使用的值已经与开始读取的值不一致。 时间 事务T1 数据库中R的值 事务T2 t0 1000 t1 FIND R t2 t3 R=R-200 t4 UPDATE R t5 800 表5.7 不可重读问题
5.3.4 封锁 产生上述三类数据不一致性的主要原因就是并发操作破坏了事务的隔离性。 并发控制就是要求DBMS提供并发控制功能以正确的方式高度并发事务,避免并发事务之间的相互干扰造成数据的不一致性,保证数据库的完整性。 5.3.4 封锁 实现并发控制的方法主要有两种:封锁(Lock)技术和时标(Timestamping)技术。这里只介绍封锁技术。 5.3.4.1 封锁类型(Lock Type) 所谓封锁就是当一个事务在对某个数据对象(可以是数据项、记录、数据集、以至整个数据库)进行操作之前,必须获得相应的锁,以保证数据操作的正确性和一致性。
封锁是目前DBMS普遍采用的并发控制方法,基本的封锁类型有两种:排它锁和共享锁。 1.排它锁(Exclusive Lock) 当事务T对某个数据对象R实现X封锁后,其他事务要等T解除X封锁以后,才能对R进行封锁。这就保证了其他事务在T释放R上的锁之前,不能再对R进行操作。 2.共享锁(Share Lock) 共享锁又称读锁,,简称为S锁,其采用的原理是允许其他用户对同一数据对象进行查询,但不能对该数据对象进行修改。 当事务T对某个数据对象R实现S封锁后,其他事务只能对R加S锁,而不能加X锁,直到T释放R上的S锁。 这就保证了其他事务在T释放R上的S锁之前,只能读取R,而不能再对R作任何修改。
5.3.4.2 封锁协议(Lock Protocol) 封锁可以保证合理的进行并发控制,保证数据的一致性。 实际上,锁是一个控制块,其中包括被加锁记录的标识符及持有锁的事务的标识符等。 在封锁时,要考虑一定的封锁规则,例如,何时开始封锁、封锁多长时间、何时释放等,这些封锁规则称为封锁协议。 对封锁方式规定不同的规则,就形成了各种不同的封锁协议。 封锁协议在不同程序上对正确控制并发操作提供了一定的保证。 上面讲述过的并发操作所带来的丢失更新、污读和不可重读等到数据不一致性问题,可以通过三级封锁协议在不同程度上给予解决,下面介绍三级封锁协议。
1.一级封锁协议 一级封锁协议的内容是:事务T在修改数据对象之前必须对其加X锁,直到事务结束。 具体地说,就是任何企图更新记录R的事务必须先执行“XLOCK R”操作,以获得对该记录进行寻址的能力并对它取得X封锁。 如果未获准“X 封锁”,那么这个事务进入等待状态,一直到获准“X封锁”,该事务才继续做下去。 该事务规定事务在更新记录R时必须获得排它性封锁,使得两个同时要求更新R的并行事务之一必须在一个事务更新操作执行完成之后才能获得X封锁,这样就避免了两个事务读到同一个R值而先后更新时所发生的丢失更新问题。
利用一级封锁协议可以解决表5.5中的数据丢失更新问题,如表5.8所示。 事务T1先对R进行X封锁(XLOCK),事务T2执行“XLOCK R”操作,未获准“X封锁”,则进入等待状态,直到事务T1更新R值以后,解除X封锁操作(UNLOCK X)。 此后事务T2再执行“XLOCK R”操作,获准“X封锁”,并对R值进行更新(此时R已是事务T1更新过的值,R=900)。 这样就能得出正确的结果。
表5.8 无丢失更新问题 时间 事务T1 数据库中R的值 事务T2 t0 XLOCK R 1000 t1 FIND R t2 t3 t1 FIND R t2 t3 R=R-100 WAIT t4 UPDATE R t5 UNLOCK X 900 t6 t7 R=R-200 T8 t9 700 表5.8 无丢失更新问题
2.二级封锁协议 一级封锁协议只有当修改数据时才进行加锁,如果只是读取数据并不加锁,所以它不能防止“污读”和“重读”数据。 二级封锁协议的内容是:在一级封锁协议的基础上,另外加上事务T在读取数据R之前必须先对其加S锁,读完后释放S锁。 所以二级封锁协议不但可以解决更新时所发生的数据丢失问题,还可以进一步防止“污读”。 利用二级封锁协议可以解决表5.6中的数据“污读”问题,如表5.9所示。 事务T1先对R进行X封锁(XLOCK),把R的值改为900,但尚未提交。这时事务T2请求对数据R加S锁,因为T1已对R加了X锁,T2只能等待,直到事务T1释放X锁。
之后事务T1因某种原因撤销,数据R恢复原值1000,并释放R上的X锁。事务T2可对数据R加S锁,读取R=1000,得到了正确的结果,从而避免了事务T2读取“脏数据”。 时间 事务T1 数据库中R的值 事务T2 t0 XLOCK R 1000 t1 FIND R t2 R=R-100 t3 UPDATE R t4 900 SLOCK R t5 ROLLBACK WAIT t6 UNLOCK R t7 T8 UNLOCK S
3.三级封锁协议 二级封锁协议在读取数据之后,立即释放S锁,所以它仍然不能防止“重读”数据。 三级封锁协议的内容是:在一级封锁协议的基础上,另外加上事务T在读取数据R之前必须先对其加S锁,读完后并不释放S锁,而直到事务T结束才释放。 所以三级封锁协议除了可以防止更新丢失问题和“污读”数据外,还可进一步防止不可重读数据,彻底解决了并发操作所带来的三个不一致性问题。 利用三级封锁协议可以解决表5.7中的不可重读问题,如表5.10所示。
在表5.10中,事务T1读取R的值之前先对其加S锁,这样其他事务只能对R加S锁,而不能加X锁,即其他事务只能读取R,而不能对R进行修改。 所以当事务T2在t3时刻申请对R加X锁时被拒绝,使其无法执行修改操作,只能等待事务T1释放R上的S锁,这时事务T1再读取数据R进行核对时,得到的值仍是1000,与开始所读取的数据是一致的,即可重读。 在事务T1释放S锁后,事务T2可以对R加X锁,进行更新操作,这样便保证了数据的一致性。
表5.10 可重读问题 时间 事务T1 数据库中R的值 事务T2 t0 1000 t1 SLOCK R t2 FIND R t3 1000 t1 SLOCK R t2 FIND R t3 XLOCK R t4 COMMIT WAIT t5 UNLOCK S t6 t7 T8 R=R-200 t9 UPDATE R t10 UNLOCK X 表5.10 可重读问题
5.3.4.3 封锁粒度(Lock Granularity) 封锁粒度指封锁的单位。 根据对数据的不同处理,封锁的对象可以是这样一些逻辑单元:字段、记录、表、数据库等。 封锁粒度与系统的并发度和并发控制的开销密切相关。 封锁粒度越小,系统中能够被封锁的对象就越多,并发度越高,但封锁机构复杂,系统开销也就越大。相反,封锁粒度越大,系统中能够被封锁的对象就越少,并发度越小,封锁机构简单,相应系统开销也就越小。 因此,在实际应用中,选择封锁粒度时应同时考虑封锁机构和并发度两个因素,对系统开销与并发度进行权衡,以求得最优的效果。 由于同时封锁一个记录的概率很小,一般数据库系统都在记录级上进行封锁,以获得更高的并发度。
5.3.4.4 死锁和活锁 封锁技术可有效解决并行操作的一致性问题,但也可产生新的问题,即活锁和死锁问题。 1.活锁(Livelock) 当某个事务请求对某一数据的排它性封锁时,由于其他事务对该数据的操作而使这个事务处于永久等待状态,这种状态称为活锁。 例如,事务T1在对数据R封锁后,事务T2又请求封锁R,于是T2等待。T3也请求封锁R。当T1释放了R上的封锁后首先批准了T3的请求,T2继续等待。然后又有又T4请求封锁R,T3释放了R上的封锁后又批准了T4的请求……T2可能永远处于等待状态,从而发生了活锁。如表5.11所示。
表5.11 活锁 时间 事务T1 事务T2 事务T3 事务T4 t0 LOCK R t1 … t2 WAIT t3 UNLOCK t4 t5 t1 … t2 WAIT t3 UNLOCK t4 t5 t6 t7 T8 表5.11 活锁
避免活锁的简单方法是采用先来先服务的策略,按照请求封锁的次序对事务排队,一旦记录上的锁释放,就使申请队列中的第一个事务获得锁。 有关活锁的问题我们不再详细讨论,因为死锁的问题较为常见,这里主要讨论有关死锁的问题。 1.死锁(Deadlock) 在同时处于等待状态的两个或多个事务中,其中的每一个在它能够进行之前,都等待着某个数据、而这个数据已被它们中的某个事务所封锁,这种状态称为死锁。 例如,事务T1在对数据R1封锁后,又要求对数据R2封锁,而事务T2已获得对数据R2的封锁,又要求对数据R1封锁,这样两个事务由于都不能得到封锁而处于等待状态,发生了死锁。如表5.12所示。
时间 事务T1 事务T2 t0 LOCK R1 t1 LOCK R2 t2 … t3 t4 WAIT t5 t6 t7 表5.12 死锁
(1)死锁产生的条件 发生死锁的必要条件有以下四条: ①互斥条件:一个数据对象一次只能被一个事务所使用,即对数据的封锁采用排它式; ②不可抢占条件:一个数据对象只能被占有它的事务所释放,而不能被别的事务强行抢占。 ③部分分配条件:一个事务已经封锁分给它的数据对象,但仍然要求封锁其他数据; ④循环等待条件:允许等待其他事务释放数据对象,系统处于加锁请求相互等待的状态。
(2)死锁的预防 死锁一旦发生,系统效率将会大大下降,因而要尽量避免死锁的发生。 在操作系统的多道程序运行中,由于多个进程的并行执行需要分别占用不同资源时,也会发生死锁。 要想预防死锁的产生,就得破坏形成死锁的条件。 同操作系统预防死锁的方法类似,在数据库环境下,常用的方法有以下两种: ①一次加锁法 一次加锁法是每个事物必须将所有要使用的数据对象全部依次加锁,并要求加锁成功,只要一个加锁不成功,表示本次加锁失败,则应该立即释放所有已加锁成功的数据对象,然后重新开始从头加锁。 一次加锁法的程序框图如图5.2。
图5.2 一次加锁法 Y N 开始加锁 加锁第1个数据 加锁成功吗? 对第m个表加锁 加锁第2个数据 释放所有已加锁的数据 …… 一次加锁成功 图5.2 一次加锁法
如表5.12发生死锁的例子,可以通过一次加锁法加以预防。 事务T1启动后,立即对数据R1和R2依次加锁,加锁成功后,执行T1,而事务T2等待。 直到T1执行完后释放R1和R2上的锁,T2继续执行。这样就不会发生死锁。 一次加锁法虽然可以有效地预防死锁的发生,但也存在一些问题。 首先,对某一事务所要使用的全部数据一次性加锁,扩大了封锁的范围,从而降低了系统的并发度。 其次,数据库中的数据是不断变化的,原来不要求封锁的数据,在执行过程中可能会变成封锁对象,所以很难事先精确地确定每个事务所要封锁的数据对象,这样只能在开始扩大封锁范围,将可能要封锁的数据全部加锁,这就进一步降低了并发度,影响了系统的运行效率。
②顺序加锁法 顺序加锁法是预先对所有可加锁的数据对象规定一个加锁顺序,每个事务都需要按此顺序加锁,在释放时,按逆序进行。 例如对于表5.12发生的死锁,我们可以规定封锁顺序为R1、R2,事务T1和T2都需要按此顺序加锁。T1先封锁R1,再封锁R2。当T2再请求封锁R1时,因为T1已经对R1加锁,T2只能等待。待T1释放R1后,T2再封锁R1,则不会发生死锁。 顺序加锁法同一次加锁法一样,也存在一些问题。因为事务的封锁请求可以随着事务的执行而动态地决定,所以很难事先确定封锁对象,从而更难确定封锁顺序。即使确定了封锁顺序,随着数据操作的不断变化,维护这些数据的封锁顺序要需要很大的系统开销。
在数据库系统中,由于可加锁的目标集合不但很大,而且是动态变化的; 可加锁的目标常常不是按名寻址,而是按内容寻址,预防死锁常要付出很高的代价,因而上述两种在操作系统中广泛使用的预防死锁的方法并不很适合数据库的特点。 一般情况下,在数据库系统中,可以允许发生死锁,在死锁发生后可以自动诊断并解除死锁。
(3)死锁的诊断与解除 数据库系统中诊断死锁的方法与操作系统类似。 可以利用事务信赖图的形式来测试系统中是否存在死锁。 例如在图5.3中,事务T1需要数据R,但R已经被事务T2封锁,那么从T1到T2划一个箭头。 如果在事务依赖图中沿着箭头方向存在一个循环,那么死锁的条件就形成了,系统就会出现死锁。 图5.3 事务依赖图 数据R T1 T2
如果已经发现死锁,DBA从依赖相同资源的事务中抽出某个事务作为牺牲品,将它撤销,解除它的所有封锁,释放此事务占用的所有数据资源,分配给其他事务,使其他事务得以继续运行下去,这样就有可能消除死锁。 在解除死锁的过程中,抽取牺牲事务的标准是根据系统状态及其应用的实际情况来确定的,通常采用的方法之一是选择一个处理死锁代价最小的事务,将其撤销。 不重要的用户,取消其操作,释放封锁的数据,恢复对数据库所作的改变。
5.4 数据库的恢复 5.4.1 数据库恢复的含义 虽然数据库系统中已采取一定的措施,来防止数据库的安全性和完整性的破坏,保证并发事务的正确执行,但数据库中的数据仍然无法保证绝对不遭受破坏,比如计算机系统中硬件的故障、软件的的错误,操作员的失误,恶意的破坏等都有可能发生,这些故障的发生影响数据库数据的正确性,甚至可能破坏数据库,使数据库中的数据全部或部分丢失。 因此,系统必须具有检测故障并把数据从错误状态中恢复到某一正确状态的功能,这就是数据库的恢复。
数据库恢复的基本原理十分简单,就是数据的冗余。 数据库中任何一部分被破坏的或不正确的数据都可以利用存储在系统其他地方的冗余数据来修复。 5.4.2 数据库恢复的原理服及其实现技术 数据库恢复的基本原理十分简单,就是数据的冗余。 数据库中任何一部分被破坏的或不正确的数据都可以利用存储在系统其他地方的冗余数据来修复。 因此恢复系统应该提供两种类型的功能: 一种是生成冗余数据,即对可能发生的故障作某些准备; 另一种是冗余重建,即利用这些冗余数据恢复数据库。 生成冗余数据最常用的技术是登记日志文件和数据转储,在实际应用中,这两种方法常常结合起来一起使用。
5.4.2.1 登记日志文件(Logging) 日志文件是用来记录事务对数据库的更新操作的文件。对数据库的每次修改,都将被修改项目的旧值和新值写在一个叫做运行日志的文件中,目的是为数据库的恢复保留详细的数据。 典型的日志文件主要包含以下内容: 1.更新数据库的事务标识(标明是哪个事务); 2.操作的类型(插入、删除或修改) 3.操作对象; 4.更新前数据的旧值(对于插入操作而言,没有旧值);
日志文件是系统运行的历史记载,必须高度可靠。 所以一般都是双副本的,并且独立地写在两个不同类型的设备上。 5.更新前数据的新值(对于删除操作而言,没有新值); 6.事务处理中的各个关键时刻(事务的开始、结束及其真正回写的时间)。 日志文件是系统运行的历史记载,必须高度可靠。 所以一般都是双副本的,并且独立地写在两个不同类型的设备上。 日志的信息量很大,一般保存在海量存储器上。 在对数据库修改时,在运行日志中要写入一个表示这个修改的运行记录。 为了防止在这两个操作之间发生故障后,运行日志中没有记录下这个修改,以后也无法撤消这个修改。为保证数据库是可恢复的,登记日志文件必须遵循两条原则原则:
所以这了安全,一定要先定日志文件,后写数据库的修改。 1.至少要等到相应运行记录的撤消部分已经写入日志文件中以后,才允许该事务往物理数据库中写入记录; 2.直到事务的所有运行记录的撤消和重做两部分都已写入日志文件中以后,才允许事务完成提交处理。 这两条原则称为日志文件的先写原则。 先写原则蕴含了如下意义:如果出现故障,只可能在日志文件中登记所做的修改,但没有修改数据库,这样在系统重新启动进行恢复时,只是撤消或重做因发生事故而没有做过的修改,并不会影响数据库的正确性。而如果先写了数据库修改,而在运行记录中没有登记这个修改,则以后就无法恢复这个修改了。 所以这了安全,一定要先定日志文件,后写数据库的修改。
5.4.2.2 数据转储(Data Dump) 数据转储是指定期地将整个数据库复制到多个存储设备如磁带、磁盘上保存起来的过程,它是数据库恢复中采用的基本手段。 转储的数据文本称为后备副本或后援副本,当数据库遭到破坏后就可利用后援副本把数据库有效地加以恢复。 转储是十分耗费时间和资源的,不能频繁地进行,应该根据数据库使用情况确定一个适当的转储周期。 按照转储方式转储可以分为海量转储和增量转储。 海量转储是指每次转储全部数据库。 增量转储每次只转储上次转储后被更新过的数据。
按照转储状态转储又可分为静态转储和动态转储。 上次转储以来对数据库的更新修改情况记录在日志文件中,利用日志文件就可进行这种转储,将更新过的那些数据重新写入上次转储的文件中,就完成了转储操作,这与转储整个数据库的效果是一样的,但花的时间要少得多。 按照转储状态转储又可分为静态转储和动态转储。 静态转储期间不允许有任何数据存取活动,因而需在当前用户事务结束之后进行,新用户事务又需在转储结束之后才能进行,这就降低了数据库的可用性。 动态转储则不同,它允许转储期间继续运行用户事务,但产生的副本并不能保证与当前状态一致。解决的办法是把转储期间各事务对数据库的修改活动登记下来,建立日志文件。 因此,备用副本加上日志文件就能把数据库恢复到某一时刻的正确状态。
5.4.3 数据库的故障和恢复的策略 数据库系统在运行中发生故障后,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库。 这时数据库就处于一种不正确的状态,或者说是不一致的状态,这时可利用日志文件和数据库转储的后备副本将数据库恢复到故障前的某个一致性状态。 数据库运行过程中可能会出现各种各样的故障,这些故障可分为以下三类:事务故障、系统故障和介质故障。 根据故障类型的不同,应该采取不同的恢复策略。
5.4.3.1 事务故障(Transaction Failure)及其恢复 事务故障表示由非预期的、不正常的程序结束所造成的故障。 造成程序非正常结束的原因包括输入数据错误、运算溢出、违反存储保护、并行事务发生死锁等。 发生事务故障时,被迫中断的事务可能已对数据库进行了修改,为了消除该事务对数据库的影响,要利用日志文件中所记载的信息,强行回滚(ROLLBACK)该事务,将数据库恢复到修改前的初始状态。 为此,要检查日志文件中由这些事务所引起的发生变化的记录,取消这些没有完成的事务所做的一切改变。 这类恢复操作称为事务撤消(UNDO),具体做法如下:
因此,一个事务是一个工作单位,也是一个恢复单位。 1.反向扫描日志文件,查找该事务的更新操作。 2.对该事务的更新操作执行反操作,即对已经插入的新记录进行删除操作,对已删除的记录进行插入操作,对修改的数据恢复旧值,用旧值代替新值。这样由后向前逐个扫描该事务己做所有更新操作,并做同样处理,直到扫描到此事务的开始标记,事务故障恢复完毕。 因此,一个事务是一个工作单位,也是一个恢复单位。 一个事务越短,越便于对它进行UNDO操作。如果一个应用程序运行时间较长,则应该把该应用程序分成多个事务,用明确的COMMIT语句结束各个事务。
5.4.3.2 系统故障(System Failure)及其恢复 系统故障是指系统在运行过程中,由于某种原因,造成系统停止运转,致使所有正在运行的事务都以非正常方式终止,要求系统重新启动。 引起系统故障的原因可能有:硬件错误如CPU故障、操作系统或DBMS代码错误、突然断电等。 这时,内存中数据库缓冲区的内容全部丢失,存储在外部存储设备上的数据库并未破坏,但内容不可靠了。
系统故障发生后,对数据库的影响有两种情况: 一种情况是一些未完成事务对数据库的更新已写入数据库,这样在系统重新启动后,要强行撤消(UNDO)所有未完成事务,清除这些事务对数据库所做的修改。这些未完成事务在日志文件中只有BEGIN TRANSCATION标记,而无COMMIT标记。 另一种情况是有些己提交的事务对数据库的更新结果还保留在缓冲区中,尚未写到磁盘上的物理数据库中,这也使数据库处于不一致状态,因此应将这些事务己提交的结果重新写入数据库。这类恢复操作称为事务的重做(REDO)。这种己提交事务在日志文件中既有BEGIN TRANSCATION标记,也有COMMIT标记。
因此,系统故障的恢复要完成两方面的工作,既要撤消所有未完成的事务,还需要重做所有己提交的事务,这样才能将数据库真正恢复到一致的状态。具体做法如下: 1.正向扫描日志文件,查找尚未提交的事务,将其事务标识记入撤消队列。同时查找已经提交的事务,将其事务标识记入重做队列。 2.对撤消队列中的各个事务进行撤消处理。方法同事务故障中所介绍的撤消方法相同。 3.对重做队列中的各个事务进行重做处理。进行重做处理的方法是:正向扫描日志文件,按照日志文件中所登记的操作内容,重新执行操作,使数据库恢复到最近某个可用状态。
但是,在故障发生前已经运行完毕的事务有些是正常结束的,有些是异常结束的。所以无需把它们全部撤消或重做。 系统发生故障后,由于无法确定哪些未完成的事务己更新过数据库,哪些事务的提交结果尚未写入数据库,这样系统重新启动后,就要撤消所有的未完成事务,重做所有的已经提交的事务。 但是,在故障发生前已经运行完毕的事务有些是正常结束的,有些是异常结束的。所以无需把它们全部撤消或重做。 通常采用设立检查点(Checkpoint)的方法来判断事务是否正常结束。每隔一段时间,比如说5分钟,系统就产生一个检查点,做下面一些事情: 1.把仍保留在日志缓冲区中的内容写到日志文件中; 2.在日志文件中写一个“检查点记录”;
每全检查点记录包含的信息有:在检查点时间的所有活动事务一览表,每个事务最近日志记录的地址。 3.把数据库缓冲区中的内容写到数据库中,即把更新的内容写到物理数据库中; 4.把日志文件中检查点记录的地址写到“重新启动文件”中。 每全检查点记录包含的信息有:在检查点时间的所有活动事务一览表,每个事务最近日志记录的地址。 在重新启动时,恢复管理程序先从“重新启动文件”中获得检查点记录的地址,从日志文件中找到该检查点记录的内容,通过日志往回找,就能决定哪些事务需要撤消,恢复到初始的状态,哪些事务需要重做。
5.4.3.3介质故障(Media Failure)及其恢复 介质故障是指系统在运行过程中,由于辅助存储器介质受到破坏,使存储在外存中的数据部分丢失或全部丢失。 这类故障比事务故障和系统故障发生的可能性要小,但这是最严重的一种故障,破坏性很大,磁盘上的物理数据和日志文件可能被破坏,这需要装入发生介质故障前最新的后备数据库副本,然后利用日志文件重做该副本后所运行的所有事务。 具体方法如下: 1.装入最新的数据库副本,使数据库恢复到最近一次转储时的可用状态。 2.装入最新的日志文件副本,根据日志文件中的内容重做已完成的事务。首先正向扫描
日志文件,找出发生故障前已提交的事务,将其记入重做队例。 再对重做队列中的各个事务进行重做处理,方法是:正向扫描日志文件,对每个重做事务重新执行登记的操作,即将日志文件中数据己更新后的值写入数据库。 通过以上对三类故障的分析,我们可以看出故障发生后对数据库的影响有两种可能性: 1.数据库没有被破坏,但数据可能处于不一致状态。这是由事务故障和系统故障引起的,这种情况在恢复时,不需要重装数据库副本,直接根据日志文件,撤销故障发生时未完成的事务,并重做己完成的事务,使数据库恢复到正确的状态。这类故障的恢复是系统在重新启动时自动完成的,不需要用户干预。 2.数据库本身被破坏。这是由介质故障引起的,这种情况在恢复时,把最近一次转储的数据装入,然后借助于日志文件,再在此基础上对数据库进行更新,从而重建了数据库。这类故障的恢复不能自动完成,需要DBA的介入,先由DBA重装最近转储的数据库副本和相应的日志文件的副本,再执行系统提供的恢复命令,具体的恢复操作由DBMS来完成。
数据库恢复的基本原理就是利用数据的冗余的,十分简单,实现的方法也比较清楚,但真正实现起来相当复杂,实现恢复的程序非常庞大,常常占整个系统代码的百分之十以上。 数据库系统所采用的恢复技术是否行之有效,不仅对系统的可靠程度起着决定性使用,而且对系统的运行效率也有很大的影响,是衡量系统性能优劣的重要指标。
小 结 数据库的重要特征是它能为多个用户提供数据共享。在多个用户使用同一数据库系统时,要保证整个系统的正常运转,DBMS必须具备一整套完整而有效的安全保护措施。本章从安全性控制、完整性控制、并发性控制和数据库恢复四方面讨论了数据库的安全保护功能。 数据库的安全性是指保护数据库,以防止因非法使用数据库所造成数据的泄露、更改或破坏。实现数据库系统安全性的方法有用户标识和鉴定、存取控制、视图定义、数据加密和审计等多种,其中,最重要的是存取控制技术和审计技术。 数据库的完整性是指保护数据库中数据的正确性、有效性和相容性。完整性和安全性是两个不同的概念,安全性措施的防范对象是非法用户和非法操作,完整性措施的防范对象是合法用户的不合语义的数据。这些语义约束构成了数据库的三条完整性规则,即触发条件、约束条件和违约响应。完整性约束条件从使用对象分为值的约束和结构的约束,从约束对象的状态又可分为静态约束和动态约束。
小 结 并发控制是为了防止多个用户同时存取同一数据,造成数据库的不一致性。事务是数据库的逻辑工作单位,并发操作中只有保证系统中一切事务的原子性、一致性、隔离性和持久性,才能保证数据库处于一致状态。并发操作导致的数据库不一致性主要有丢失更新、污读和不可重读三种。实现并发控制的方法主要是封锁技术,基本的封锁类型有排它锁和共享锁两种,三个级别的封锁协议可以有效解决并发操作的一致性问题。对数据对象施加封锁,会带来活锁和死锁问题,并发控制机制可以通过采取一次加锁法或顺序加锁法预防死锁的产生。死锁一旦发生,可以选择一个处理死锁代价最小的事务将其撤销。 数据库的恢复是指系统发生故障后,把数据从错误状态中恢复到某一正确状态的功能。对于事务故障、系统故障和介质故障三种不同的故障类型,DBMS有不同的恢复方法。登记日志文件和数据转储是恢复中常用的技术,恢复的基本原理是利用存储在日志文件和数据库后备副本中的冗余数据来重建数据库。