WEB开发网
开发学院数据库DB2 DB2 9 基础(730 考试)认证指南,第 6 部分: 数据... 阅读

DB2 9 基础(730 考试)认证指南,第 6 部分: 数据并发性

 2009-01-22 16:38:59 来源:WEB开发网   
核心提示:事务理解数据一致性什么是数据一致性?回答这个问题的最佳方法是通过研究示例,假定您的公司拥有多家连锁饭店,DB2 9 基础(730 考试)认证指南,第 6 部分: 数据并发性,公司用一个数据库来跟踪每家饭店中的货物存储量,为了使货物采购过程更方便,第 3 部分: 访问 DB2 数据 DB2 9 基础(730 考试)认证指

事务

理解数据一致性

什么是数据一致性?回答这个问题的最佳方法是通过研究示例。假定您的公司拥有多家连锁饭店,公司用一个数据库来跟踪每家饭店中的货物存储量。为了使货物采购过程更方便,数据库包含每个连锁店的库存表。每当一家饭店收到或用掉一部分货物时,与该饭店相应的库存表就会被修改以反映库存变化。

现在,假定从一家店调配若干瓶番茄酱到另一家店。为了准确地表示这一次库存调配,调出方饭店表中存储的番茄酱瓶数必须减少,而接收方饭店表中存储的番茄酱瓶数必须增加。如果用户减少了调出方饭店库存表中的番茄酱瓶数,但没有增加接收方库存表中的番茄酱瓶数,则数据就会变得不一致。此时所有连锁店的番茄酱的总瓶数不再准确了。

如果用户忘记了进行所有必要的更改(正如在前面的示例中一样),或者如果在用户进行更改的过程中系统崩溃了,又或者如果数据库应用程序由于某种原因过早地停止了,数据库中的数据都会变得不一致。当几个用户同时访问相同的数据库表时,也可能发生不一致。为了防止数据的不一致(尤其是在多用户环境中),DB2 的设计中集成了下列数据一致性支持机制:

事务

隔离级别

事务和事务边界

事务(也称为工作单元)是一种将一个或多个 SQL 操作组合成一个单元的可恢复操作序列,通常位于应用程序进程中。事务的启动和终止定义了数据库一致性点;要么将一个事务中执行的所有 SQL 操作的结果都应用于数据库(提交),要么完全取消并丢弃已执行的所有 SQL 操作的结果(回滚)。

使用从 Command Center、Script Center 或 Command Line Processor 运行的嵌入式 SQL 应用程序和脚本,在可执行 SQL 语句第一次执行时(在建立与数据库的连接之后或在现有事务终止之后),事务就会自动启动。在启动事务之后,必须由启动事务的用户或应用程序显式地终止它,除非使用了称为自动提交(automatic commit) 的过程(在这种情况下,发出的每个单独的 SQL 语句被看作单个事务,它一执行就隐式地提交了)。

在大多数情况下,通过执行 COMMIT 或 ROLLBACK 语句来终止事务。当执行 COMMIT 语句时,自从事务启动以来对数据库所做的一切更改就成为永久性的了 —— 即,它们被写到磁盘。当执行 ROLLBACK 语句时,自从事务启动以来对数据库所做的一切更改都被撤消,而数据库返回到事务开始之前所处的状态。不管是哪种情况,数据库在事务完成时都保证能回到一致状态。

一定要注意一点:虽然事务通过确保对数据的更改仅在事务被成功提交之后才成为永久性的,从而提供了一般的数据库一致性,但还是需要用户或应用程序来确保每个事务中执行的 SQL 操作序列始终会导致一致的数据库。

COMMIT 和 ROLLBACK 操作的效果

正如在前面提到的,通常通过执行 COMMIT 或 ROLLBACK SQL 语句来终止事务。为了理解这些语句如何工作,研究一个示例是有帮助的。

如果按所示的顺序执行下列 SQL 语句:

清单 1. 由三个事务组成的简单工作负载

CONNECT TO MY_DB
CREATE TABLE DEPARTMENT (DEPT_ID INTEGER NOT NULL, DEPT_NAME VARCHAR(20))
INSERT INTO DEPARTMENT VALUES(100, 'PAYROLL')
INSERT INTO DEPARTMENT VALUES(200, 'ACCOUNTING')
COMMIT  
    
INSERT INTO DEPARTMENT VALUES(300, 'SALES')
ROLLBACK 
    
INSERT INTO DEPARTMENT VALUES(500, 'MARKETING')
COMMIT

这将创建一个名为 DEPARTMENT 的表,它的结构如下所示:

DEPT_ID DEPT_NAME
100PAYROLL
200ACCOUNTING
500MARKETING

这是因为当执行第一个 COMMIT 语句时,创建名为 DEPARTMENT 的表并向 DEPARTMENT 表中插入两条记录,这两个操作都会变成永久性的。另一方面,当执行 ROLLBACK 语句时,删除插入 DEPARTMENT 表中的第三条记录,该表返回到执行插入操作之前所处的状态。最后,当执行第二个 COMMIT 语句时,插入到 DEPARTMENT 中的第四条记录成为永久性的,而数据库再次返回到一致状态。

正如可以从这个示例中看到的,提交或回滚操作只影响在这个操作所结束的事务内做出的更改。只要数据更改仍然未被提交,其他用户和应用程序通常就无法看见它们(也有例外情况,稍后我们将进行讨论),并可以通过执行回滚操作取消它们。但是,一旦数据更改被提交了,其他用户和应用程序就可以访问它们,并且再也不能通过回滚操作取消它们了。

不成功事务的效果

我们刚才已看到当通过 COMMIT 或 ROLLBACK 语句终止事务时会发生什么。但是,如果在事务完成前出现系统故障,那会发生什么情况呢?在这种情况下,DB2 数据库管理程序会取消所有未提交的更改,从而恢复数据库一致性(假定在事务启动时存在这样的一致性)。图 1 对比了成功的事务和在成功终止之前失败的事务的效果。

图 1. 对比成功的和不成功的事务

DB2 9 基础(730 考试)认证指南,第 6 部分: 数据并发性

并发性和隔离级别

当多个用户访问同一数据库时会发生的现象

在单用户环境中,每个事务都是顺序执行的,而不会遇到与其他事务的冲突。但是,在多用户环境下,多个事务可以(而且常常)同时执行。因此每个事务都有可能与其他正在运行的事务发生冲突。有可能与其他事务发生冲突的事务称为交错的 或并行的 事务,而相互隔离的事务称为串行化 事务,这意味着同时运行它们的结果与一个接一个连续地运行它们的结果没有区别。在多用户环境下,在使用并行事务时,会发生四种现象:

丢失更新:这种情况发生在两个事务读取并尝试更新同一数据时,其中一个更新会丢失。例如:事务 1 和事务 2 读取同一行数据,并都根据所读取的数据计算出该行的新值。如果事务 1 用它的新值更新该行以后,事务 2 又更新了同一行,则事务 1 所执行的更新操作就丢失了。由于设计 DB2 的方法,DB2 不允许发生此类现象。

脏读:当事务读取尚未提交的数据时,就会发生这种情况。例如:事务 1 更改了一行数据,而事务 2 在事务 1 提交更改之前读取了已更改的行。如果事务 1 回滚该更改,则事务 2 就会读取被认为是不曾存在的数据。

不可重复的读:当一个事务两次读取同一行数据,但每次获得不同的数据值时,就会发生这种情况。例如:事务 1 读取了一行数据,而事务 2 在更改或删除该行后提交了更改。当事务 1 尝试再次读取该行时,它会检索到不同的数据值(如果该行已经被更新的话),或发现该行不复存在了(如果该行被删除的话)。

幻像:当最初没有看到某个与搜索条件匹配的数据行,而在稍后的读操作中又看到该行时,就会发生这种情况。例如:事务 1 读取满足某个搜索条件的一组数据行,而事务 2 插入了与事务 1 的搜索条件匹配的新行。如果事务 1 再次执行产生原先行集的查询,就会检索到不同的行集。

维护数据库的一致性和数据完整性,同时又允许多个应用程序同时访问同一数据,这样的特性称为并发性。DB2 数据库用来尝试强制实施并发性的方法之一是通过使用隔离级别,它决定在第一个事务访问数据时,如何对其他事务锁定或隔离该事务所使用的数据。DB2 使用下列隔离级别来强制实施并发性:

可重复的读(Repeatable Read)

读稳定性(Read Stability)

游标稳定性(Cursor Stability)

未提交的读(Uncommitted Read)

可重复的读隔离级别可以防止所有现象,但是会大大降低并发性的程度(可以同时访问同一资源的事务数量)。未提交的读隔离级别提供了最大的并发性,但是后三种现象都可能出现。

可重复读隔离级别

可重复读隔离级别是最严格的隔离级别。在使用它时,一个事务的影响完全与其他并发事务隔离:脏读、不可重复的读、幻像都不会发生。当使用可重复的读隔离级别时,在事务执行期间锁定该事务以任何方式 引用的所有行。因此,如果在同一个事务中发出同一个 SELECT 语句两次或更多次,那么产生的结果数据集总是相同的。因此,使用可重复的读隔离级别的事务可以多次检索同一行集,并可以对它们执行任意操作,直到由提交或回滚操作终止事务。但是,在事务存在期间,不允许其他事务执行会影响这个事务正在访问的任何行的插入、更新或删除操作。为了确保这种行为,锁定该事务所引用的每一行 —— 而不是仅锁定被实际检索或修改的那些行。因此,如果一个事务扫描了 1000 行,但只检索 10 行,则所扫描的 1000 行(而不仅是被检索的 10 行)都会被锁定。

那么在现实环境中可重复读隔离级别是如何工作的呢?假定您使用 DB2 数据库跟踪旅馆记录,包括房间预订和房价信息,还有一个基于 Web 的应用程序,它允许顾客按 “先到先服务” 的原则预订房间。如果旅馆预订应用程序是在可重复读隔离级别下运行的,当顾客扫描某个日期段内的可用房间列表时,您(旅馆经理)将无法更改那些房间在指定日期范围内的房价。同样,其他顾客也无法进行或取消将会更改该列表的预订(直到第一个顾客的事务终止为止)。但是,对于生成第一个顾客的列表时没有读取的任何房间记录,您可以修改房价。同样,其他顾客也可以进行或取消这些房间的预订。图 2 说明了这种行为。

图 2. 可重复读隔离级别的示例

DB2 9 基础(730 考试)认证指南,第 6 部分: 数据并发性

读稳定性隔离级别

读稳定性隔离级别没有可重复读隔离级别那么严格;因此,它没有将事务与其他并发事务的效果完全隔离。读稳定性隔离级别可以防止脏读和不可重复的读,但是可能出现幻像。在使用这个隔离级别时,只锁定事务实际检索和修改的行。因此,如果一个事务扫描了 1000 行,但只检索 10 行,则只有被检索的 10 行(而不是所扫描的 1000 行)被锁定。因此,如果在同一个事务中发出同一个 SELECT 语句两次或更多次,那么每次产生的结果数据集可能不同。

与可重复读隔离级别一样,在读稳定性隔离级别下运行的事务可以检索一个行集,并可以对它们执行任意操作,直到事务终止。在这个事务存在期间,其他事务不能执行那些会影响这个事务检索到的行集的更新或删除操作;但是其他事务可以执行插入操作。如果插入的行与第一个事务的查询的选择条件匹配,那么这些行可能作为幻像出现在后续产生的结果数据集中。其他事务对其他行所做的更改,在提交之前是不可见的。

那么,读稳定性隔离级别会如何影响旅馆预订应用程序的工作方式呢?当一个顾客检索某个日期段内的所有可用房间列表时,您可以更改这个顾客的列表之外的任何房间的房价。同样,其他顾客可以进行或取消房间预订,如果第一个顾客再次运行同样的查询,其他顾客的操作可能会影响他获得的可用房间列表。如果第一个顾客再次查询同一个日期段内的所有可用房间列表,产生的列表中有可能包含新的房价或第一次产生列表时不可用的房间。图 3 说明了这种行为。

图 3. 读稳定性隔离级别的示例

DB2 9 基础(730 考试)认证指南,第 6 部分: 数据并发性

游标稳定性隔离级别

游标稳定性隔离级别在隔离事务效果方面非常宽松。它可以防止脏读;但有可能出现不可重复的读和幻像。这是因为在大多数情况下,游标稳定性隔离级别只锁定事务声明并打开的游标当前引用的行。

当使用游标稳定性隔离级别的事务通过游标从表中检索行时,其他事务不能更新或删除游标所引用的行。但是,如果被锁定的行本身不是用索引访问的,那么其他事务可以将新的行添加到表中,以及对被锁定行前后的行进行更新和/或删除操作。所获取的锁一直有效,直到游标重定位或事务终止为止。(如果游标重定位,原来行上的锁就被释放,并获得游标现在引用的行上的锁。)此外,如果事务修改了它检索到的任何行,那么在事务终止之前,其他事务不能更新或删除该行,即使在游标不再位于被修改的行。与可重复读和读稳定性隔离级别一样,其他事务在其他行上进行的更改,在这些更改提交之前对于使用游标稳定性隔离级别的事务(这是默认的隔离级别)是不可见的。

如果旅馆预订应用程序在游标稳定性隔离级别下运行,那么有什么影响呢?当一个顾客检索某个日期段内所有可用房间的列表,然后查看关于产生的列表上每个房间的信息时(每次查看一个房间),您可以更改旅馆中任何房间的房价,但是这个顾客当前正在查看的房间除外 (对于指定的日期段)。同样,其他顾客可以对任何房间进行或取消预订,但是这个顾客当前正在查看的房间除外 (对于指定的日期段)。但是,对于第一个顾客当前正在查看的房间记录,您和其他顾客都不能进行任何操作。当第一个顾客查看列表中另一个房间的信息时,您和其他顾客就可以修改他刚才查看的房间记录(如果这个顾客没有预订这个房间的话);但不能修改第一个顾客当前正在查看的房间记录。图 4 说明了这种行为。

图 4. 游标稳定性隔离级别的示例

DB2 9 基础(730 考试)认证指南,第 6 部分: 数据并发性

未提交的读隔离级别

未提交的读隔离级别是最不严格的隔离级别。实际上,在使用这个隔离级别时,仅当另一个事务试图删除或更改被检索的行所在的表时,才会锁定一个事务检索的行。因为在使用这种隔离级别时,行通常保持未锁定状态,所以脏读、不可重复的读和幻像都可能会发生。因此,未提交的读隔离级别通常用于那些访问只读表和视图的事务,以及某些执行 SELECT 语句的事务(只要其他事务的未提交数据对这些语句没有负面效果)。

顾名思义,其他事务对行所做的更改在已经提交之前对于使用未提交的读隔离级别的事务是可见的。但是,此类事务不能看见或访问其他事务所创建的表、视图或索引,直到那些事务被提交为止。类似地,如果其他事务删除了现有的表、视图或索引,那么仅当进行删除操作的事务终止时,使用未提交的读隔离级别的事务才能知道这些对象不再存在了。(一定要注意一点:当运行在未提交的读隔离级别下的事务使用可更新游标时,该事务的行为和在游标稳定性隔离级别下运行一样,并应用游标稳定性隔离级别的约束。)

那么未提交的读隔离级别对旅馆预订应用程序有什么影响呢?现在,当一个顾客检索某个日期段内的所有可用房间列表时,您可以更改旅馆中任何房间任何日期的房价,而其他顾客也可以对任何房间进行或取消预订,包括第一个顾客当前正在查看的房间记录(对于指定的日期段)。另外,第一个顾客生成的房间列表可能包含其他顾客正在预订(因此实际上不可用)的房间。图 5 说明了这种行为。

图 5. 未提交的读隔离级别示例

DB2 9 基础(730 考试)认证指南,第 6 部分: 数据并发性

选择正确的隔离级别

使用的隔离级别不仅影响数据库对并发性的支持如何,而且影响并发应用程序的性能。通常,使用的隔离级别越严格,并发性就越小,某些应用程序的性能可能会越低,因为它们要等待资源上的锁被释放。那么,如何决定要使用哪种隔离级别呢?最好的方法是确定哪些现象是不可接受的,然后选择能够防止这些现象发生的隔离级别:

如果正在执行大型查询,而且不希望并发事务所做的修改导致查询的多次运行返回不同的结果,则使用可重复的读隔离级别。

如果希望在应用程序之间获得一定的并发性,还希望限定的行在事务执行期间保持稳定,则使用读稳定性隔离级别。

如果希望获得最大的并发性,同时不希望查询看到未提交的数据,则使用游标稳定性隔离级别。

如果正在只读的表/视图/数据库上执行查询,或者并不介意查询是否返回未提交的数据,则使用未提交的读隔离级别。

指定要使用的隔离级别

尽管隔离级别控制事务级上的行为,但实际上是在应用程序级指定它们的:

对于嵌入式 SQL 应用程序,在预编译时或在将应用程序绑定到数据库(如果使用延迟绑定)时指定隔离级别。在这种情况下,使用 PRECOMPILE 或 BIND 命令 的 ISOLATION 选项来设置隔离级别。

对于开放数据库连接(Open Database Connectivity,ODBC)和调用级接口(Call Level Interface,CLI)应用程序,隔离级别是在应用程序运行时通过调用指定了 SQL_ATTR_TXN_ISOLATION 连接属性的 SQLSetConnectAttr() 函数进行设置的。(另外,也可以通过指定 db2cli.ini 配置文件中的 TXNISOLATION 关键字的值来设置 ODBC/CLI 应用程序的隔离级别;但是,这种方法不够灵活,不能像第一种方法那样为一个应用程序中的不同事务修改隔离级别。)

对于 Java 数据库连接(Java Database Connectivity,JDBC)和 SQLJ 应用程序,隔离级别是在应用程序运行时通过调用 DB2 的 java.sql 连接接口中的 setTransactionIsolation() 方法设置的。

当没有使用这些方法显式指定应用程序的隔离级别时,默认使用游标稳定性隔离级别。这个默认设置应用于从命令行处理程序(CLP)执行的 DB2 命令、SQL 语句和脚本以及嵌入式 SQL、ODBC/CLI、JDBC 和 SQLJ 应用程序。因此,也可以为从 CLP 执行的操作(以及传递给 DB2 CLP 进行处理的脚本)指定隔离级别。在这种情况下,隔离级别是通过在建立数据库连接之前在 CLP 中执行 CHANGE ISOLATION 命令设置的。

在 DB2 UDB 8.1 及更高版本中,能够指定特定查询所用的隔离级别,方法是在 SELECT SQL 语句中加上 WITH [RR | RS | CS | UR] 子句。使用这个子句的简单 SELECT 语句示例如下所示:

SELECT * FROM EMPLOYEE WHERE EMPID = '001' WITH RR

如果应用程序在大多数时候需要比较宽松的隔离级别(以支持最大的并发性),但是对于其中的某些查询必须防止某些现象出现,那么这个子句就是帮助您实现目标的好方法。

锁的工作原理

在 并发性和隔离级别 一节中,我们看到 DB2 通过使用锁 把事务彼此隔离开来。锁是一种用来将数据资源与单个事务关联起来的机制,其用途是当某个资源与拥有它的事务关联在一起时,控制其他事务如何与该资源进行交互。(我们称与被锁定的资源关联的事务持有 或拥有 该锁。)DB2 数据库管理程序用锁来禁止事务访问其他事务写入的未提交数据(除非使用了未提交的读隔离级别),并禁止其他事务在拥有锁的事务使用限制性隔离级别时对这些行进行更新。一旦获取了锁,在事务终止之前,就一直持有该锁;该事务终止时释放锁,其他事务就可以使用被解锁的数据资源了。

如果一个事务尝试访问数据资源的方式与另一个事务所持有的锁不兼容(稍后我们将研究锁兼容性),则该事务必须等待,直到拥有锁的事务终止为止。这被称为锁等待 事件。当锁等待事件发生时,尝试访问数据资源的事务所做的只是停止执行,直到拥有锁的事务终止和不兼容的锁被释放为止。

锁的属性

所有的锁都有下列基本属性:

object:object 属性标识要锁定的数据资源。DB2 数据库管理程序在需要时锁定数据资源(如表空间、表和行)。

size:size 属性指定要锁定的数据资源部分的物理大小。锁并不总是必须控制整个数据资源。例如,DB2 数据库管理程序可以让应用程序独占地控制表中的特定行,而不是让该应用程序独占地控制整个表。

duration:duration 属性指定持有锁的时间长度。事务的隔离级别通常控制着锁的持续时间。

mode:mode 属性指定允许锁的拥有者执行的访问类型,以及允许并发用户对被锁定数据资源执行的访问类型。这个属性通常称为锁状态。

锁状态

锁状态确定允许锁的拥有者执行的访问类型,以及允许并发用户对被锁定数据资源执行的访问类型。表 1 说明了可用的锁状态,按照控制递增的次序排列。

表 1. 锁状态

锁状态(模式)适用对象描述
意向无(Intent None,IN)表空间和表锁的拥有者可以读取被锁定表中的数据(包括未提交数据),但不能更改这些数据。在这种模式中,锁的拥有者不获取行级锁;因此,其他并发应用程序可以读取和更改表中的数据。
意向共享(Intent Share,IS) 表空间和表锁的拥有者可以读取被锁定表中的数据,但不能更改这些数据。同样,因为锁的拥有者不获取行级锁;所以,其他并发的应用程序可以读取和更改表中的数据。(当事务拥有表上的意向共享锁时,就在它所读取的每个行上进行共享锁定。)当事务没有表达更新表中行的意图时,就获取这种锁。(SELECT FOR UPDATE、UPDATE ... WHERE 和 INSERT 语句表达更新的意图。)
下一键共享(Next Key Share,NS)锁拥有者和所有并发的事务都可以读(但不能更改)被锁定行中的数据。这种锁用来在使用读稳定性或游标稳定性事务隔离级别读取的数据上代替共享锁。
共享(Share,S)表和行锁拥有者和任何其他并发的事务都可以读(但不能更改)被锁定的表或行中的数据。只要表不是使用共享锁锁定的,那么该表中的单个行可以使用共享锁锁定。但是,如果表是用共享锁定的,则锁拥有者不能在该表中获取行级的共享锁。如果表或行是用共享锁锁定的,则其他并发事务可以读取数据,但不能对它进行更改。
意向互斥(Intent Exclusive,IX)表空间和表锁拥有者和任何其他并发的应用程序都可以读取和更改被锁定表中的数据。当锁拥有者从表中读取数据时,它在所读取的每一行上获取一个共享锁,而在它更新的每一行上获取更新锁和互斥锁。其他并发的应用程序可以读取和更新被锁定的表。当事务表达更新表中行的意图时,就获取这种锁。
带意向互斥的共享(Share With Intent Exclusive,SIX)锁拥有者可以读取和更改被锁定表中的数据。锁拥有者在它更新的行上获取互斥锁,但不在它读取的行上获取锁;因此,其他并发的应用程序可以读取但不能更新被锁定表中的数据。
更新(Update,U)表和行锁的拥有者可以更新被锁定表中的数据,而且锁的拥有者在它所更新的任何行上自动获得互斥锁。其他并发的应用程序可以读取但不能更新被锁定表中的数据。
下一键弱互斥(Next Key Weak Exclusive,NW)锁的拥有者可以读取但不能更新被锁定的行。当在非编目表的索引中插入行时,在表中的下一行上获得这种锁。
互斥(Exclusive,X)表和行锁的拥有者可以读取和更改被锁定的表或行中的数据。如果获取了互斥锁,则只允许使用未提交的读隔离级别的应用程序访问被锁定的表或行。对于用 INSERT、UPDATE 和/或 DELETE 语句操作的数据资源,将获取互斥锁。
弱互斥(Weak Exclusive,WE)锁的拥有者可以读取和更改被锁定的行。当向非编目表中插入行时,该行上将获得这种锁。
超级互斥(Super Exclusive,Z)表空间和表锁的拥有者可以更改表、删除表、创建索引或删除索引。当事务尝试执行上述任何一种操作时,表上就自动获得这种锁。在释放这个锁之前,不允许其他并发事务读取或更新该表。

如何获取锁

在大多数情况下,DB2 数据库管理程序在需要锁时隐式地获取它们,因此这些锁在 DB2 数据库管理程序的控制之下。除了使用未提交读隔离级别的情况外,事务从不需要显式地请求锁。实际上,惟一有可能被事务显式地锁定的数据库对象是表。图 6 说明了用何种逻辑确定为所引用的对象获取什么类型的锁。

图 6. 如何获取锁

DB2 9 基础(730 考试)认证指南,第 6 部分: 数据并发性

DB2 数据库管理程序总是尝试获取行级锁。但是,可以通过执行特殊形式的 ALTER TABLE 语句来修改这种行为,如下所示:

ALTER TABLE [TableName] LOCKSIZE TABLE

其中的 TableName 标识一个现有表的名称,所有事务在访问它时都要获取表级锁。

也可以通过执行 LOCK TABLE 语句,强制 DB2 数据库管理程序为特定事务在表上获取表级锁,如下所示:

LOCK TABLE [TableName] IN [SHARE | EXCLUSIVE] MODE

其中的 TableName 标识一个现有表的名称,对于这个表应该获取表级锁(假定其他事务在该表上没有不兼容的锁)。如果在执行这个语句时指定了共享(SHARE)模式,就会获得一个允许其他事务读取(但不能更改)存储在表中的数据的表级锁;如果执行时指定了互斥(EXCLUSIVE)模式,就会获得一个不允许其他事务读取或修改存储在表中的数据的表级锁。

锁和性能

锁兼容性

如果数据资源上的一种锁状态允许在同一资源上放置另一个锁,就认为这两种锁(或两种状态)是兼容的。每当一个事务持有数据资源上的锁,而第二个事务请求同一资源上的锁时,DB2 数据库管理程序会检查两种锁状态以判断它们是否兼容。如果锁是兼容的,则将锁授予第二个事务(假定没有其他事务在等待该数据资源)。但是,如果锁不兼容,则第二个事务必须等待,直到第一个事务释放它的锁为止,然后它才可以获取对资源的访问权并继续处理。(如果资源上有多个与新请求的锁不兼容的锁,则第二个事务必须等到它们全部被释放为止。)请参阅 IBM DB2 9 Administration Guide: Performance 文档(或在 DB2 信息中心搜索 Lock type compatibility 主题)以获取关于各个锁之间是否兼容的特定信息。

锁转换

当事务尝试访问它已经持有锁的数据资源,但是所需的访问模式需要比已持有的锁更严格的锁时,则所持有的锁的状态更改成更严格的状态。将已经持有的锁的状态更改成更严格状态的操作称为锁转换。发生锁转换是因为一个事务同一时间内只能在一个数据资源上持有一个锁。

在大多数情况下,对行级锁执行锁转换,转换过程相当简单。例如,如果持有共享(S)或更新(U)行级锁,但是需要互斥(X)锁,则所持有的锁将被转换成互斥(X)锁。但意向互斥(IX)锁和共享(S)锁是特例,因为无法确定其中哪个更严格。因此,如果持有其中一种行级锁但又需要另一种锁,则所持有的锁将转换成带意向互斥的共享(SIX)锁。假定所请求的锁状态更严格的话,则类似的转换会使所请求的锁状态成为持有锁的新状态。(仅当所持有的锁可以增加其严格性时,才会发生锁转换。)在转换了锁状态之后,锁处于所获取的最高状态,直到持有该锁的事务终止为止。

锁升级

所有的锁都需要存储空间;因为可用空间并不是无限的,所以 DB2 数据库管理程序必须限制锁可以使用的空间(这是通过 maxlocks 数据库配置参数完成的)。为了防止特定数据库代理超过已建立的锁空间限制,当获取的(任意类型的)锁过多时,会自动执行称为锁升级 的过程。锁升级是一种转换,它将同一表内几个单独的行级锁转换成一个单一的表级锁。因为锁升级是在内部处理的,所以惟一可从外部检测到的结果可能只是对一个和多个表的并发访问减少了。

以下是锁升级的工作原理:当事务请求锁而锁存储空间已满时,就选择与该事务相关联的一个表,让它获取一个表级锁,释放该表的所有行级锁(从而在锁列表数据结构中让出空间),并将表级锁添加到锁列表。如果这个过程所释放的空间不够,则选择另一个表,重复这个过程,直到释放了足够的可用空间为止。这时,事务将获取所请求的锁并继续执行。但是,如果在该事务的所有行级锁都已经升级之后,仍然没有获得必要的可用锁空间,则(通过 SQL 错误编码)要求事务提交或回滚它启动以来所做的所有更改,然后事务终止。

锁超时

每当一个事务在特定数据资源(例如,表或行)上持有锁时,直到持有锁的事务终止并释放它所获取的所有锁之前,其他事务对该资源的访问都可能被拒绝。如果没有某种锁超时检测机制,则事务可能无限期地等待锁的释放。例如,有可能出现这种情况:一个事务在等待另一个用户的应用程序所持有的锁被释放,而该用户离开了他或她的工作站,但忘了执行一些允许应用程序终止拥有锁的事务的交互。显然,此类情况会导致极差的应用程序性能。要避免发生此类情况时阻碍其他应用程序的执行,可以在数据库的配置文件中指定锁超时值(通过 locktimeout 数据库配置参数)。该参数控制任何事务等待获取所请求的锁的时间。如果在指定的时间间隔过去之后还未获得想要的锁,则等待的应用程序接收一个错误,并回滚请求该锁的事务。分布式事务应用程序环境尤其容易产生此类情况;可以通过使用锁超时避免它们。

死锁

尽管可以通过建立锁超时来避免一个事务无限期地等待另一个事务释放锁的情况,但是锁超时无法解决两个或更多事务对锁的争用。这种情况称为死锁 或死锁循环。说明死锁的发生原因的最佳方式是举例说明:假定事务 1 在表 A 上获取了互斥(X)锁,而事务 2 在表 B 上获取了互斥(X)锁。现在,假定事务 1 尝试在表 B 上获取互斥(X)锁,而事务 2 尝试在表 A 上获取互斥(X)锁。这两个事务的处理都将被挂起,直到同意第二个锁请求为止。但是,因为在任何一个事务释放它目前持有的锁(通过执行或回滚操作)之前,这两个事务的锁请求都不会被同意,而且因为这两个事务都不能释放它目前持有的锁(因为它们都已挂起并等待锁),所以它们都陷入了死锁循环。图 7 说明了这个死锁场景。

图 7. 死锁循环

DB2 9 基础(730 考试)认证指南,第 6 部分: 数据并发性

当死锁循环发生时,除非某些外部代理进行干涉,否则所涉及的所有事务将无限期地等待释放锁。在 DB2 UDB 中,用于处理死锁的代理是称为死锁检测器 的异步系统后台进程。死锁检测器的惟一职责是定位和解决在锁定子系统中找到的任何死锁。每个数据库有自己的死锁检测器,它在数据库初始化过程中激活。激活之后,死锁检测器在大多数时间处于 “休眠” 状态,但会以预置的时间间隔被 “唤醒”,以确定锁定子系统中是否存在死锁循环。如果死锁检测器在锁定子系统中发现死锁,则随机选择死锁涉及的一个事务,终止并回滚它。选择的事务收到一个 SQL 错误编码,它所获得的所有锁都被释放;这样,剩下的事务就可以继续执行了,因为死锁循环已经被打破了。

锁粒度

正如先前提到的,每当一个事务在特定数据资源上持有锁时,在持有锁的事务终止之前,其他事务对该资源的访问都可能被拒绝。因此,为了进行优化以获取最大的并发性,行级锁通常比表级锁更好,因为它们所限制访问的资源要小得多。但是,因为所获取的每个锁都需要一定数量的处理时间和存储空间,才能获取锁并进行管理,所以单个表级锁需要的开销比几个单独的行级锁少。除非另外指定,否则默认情况下获取行级锁。

可以通过使用 ALTER TABLE ... LOCKSIZE TABLE、ALTER TABLE ... LOCKSIZE ROW 和 LOCK TABLE 语句控制锁的粒度(即,获取行级锁还是表级锁)。ALTER TABLE ... LOCKSIZE TABLE 语句提供了确定粒度的全局方法,它使得所有访问特定表中行的事务都获取表级锁。另一方面,LOCK TABLE 语句允许在单个事务级别获取表级锁。在使用这两种语句时,事务在需要锁时就获取单个共享(S)或互斥(X)表级锁。结果,因为只需获取和释放一个表级锁,而不是多个不同的行级锁,所以锁定性能通常会提高。但是,在使用表级锁时,如果长时间运行的事务获取互斥而不是共享表级锁,那么并发性会降低。

事务和锁定

从锁定的角度来看,所有事务通常归为以下几类之一:

只读:这是指只读性的事务,它们包含 SELECT 语句(它们本质上就是只读的)、指定了 FOR READ ONLY 子句的 SELECT 语句或意义虽不明确但因为在预编译和/或绑定过程中指定了 BLOCKING 选项而看作是只读的 SQL 语句。

倾向于更改:这是指有可能进行更改的事务,它们包含指定了 FOR UPDATE 子句的 SELECT 语句或者那些意义虽不明确但因为 SQL 预编译器解释它的方式而看作是倾向于进行更改的 SQL 语句。

更改:这是指一定会进行更改的事务,它们包含 INSERT、UPDATE 和/或 DELETE 语句,但不包括 UPDATE ... WHERE CURRENT OF ... 或 DELETE ... WHERE CURRENT OF ... 语句。

游标控制:这是指包含 UPDATE ... WHERE CURRENT OF ... 和 DELETE ... WHERE CURRENT OF ... 语句的事务。

只读事务通常使用意向共享(IS)和/或共享(S)锁。另一方面,倾向于更改的事务将更新(U)、意向互斥(IX)和互斥(X)锁用于表,将共享(S)、更新(U)和互斥(X)锁用于行。更改事务往往使用意向互斥(IX)和/或互斥(X)锁,而游标控制的事务通常使用意向互斥(IX)和/或互斥(X)锁。

当 SQL 语句准备执行时,DB2 优化器研究各种满足该语句请求的方法,并估计每种方法所涉及的执行成本。然后,DB2 优化器根据这一评估选择它认为最优的访问计划(访问计划指定满足 SQL 请求所需的操作,以及执行这些操作的顺序)。访问计划可以使用两种方法之一来访问表中的数据:通过直接地读取表(称为执行表 或关系扫描);或通过读取该表上的索引,然后检索特定索引项所引用的表行(称为执行索引扫描)。

DB2 优化器选择的访问路径(通常是根据数据库的设计确定的)会对所获取锁的数目和所使用的锁状态产生显著的影响。例如,当使用索引扫描来查找特定行时,DB2 数据库管理程序极有可能获取一个或多个意向共享(IS)行级锁。但是,如果使用表扫描,因为必须依次扫描整个表来找到特定行,所以 DB2 数据库管理程序可能会选择获取单个共享(S)表级锁。

结束语

本教程旨在介绍数据一致性的概念,以及 DB2 9 在单用户和多用户环境下用来维护数据库一致性的各种机制。如果用户忘记了进行所有必要的更改,或者如果在用户进行更改的过程中系统崩溃了,又或者数据库应用程序由于某种原因过早地停止了,数据库都会变得不一致。当几个用户同时访问同一数据库时,也可能发生不一致的情况。例如,一个用户可能在适当地更新所有表之前读取另一个用户的更改,并根据所读取的不是最终的数据值进行了一些不适当的操作或进行了错误的更改。为了防止数据不一致(尤其是在多用户环境中),DB2 9 的开发人员将下列数据一致性支持机制合并到其设计中:

事务

隔离级别

事务(也称为工作单元)是一种将一个或多个 SQL 操作组织成一个单元的可恢复序列,通常位于应用程序进程中。事务的启动和终止定义了数据库一致性点;要么将事务中执行的所有 SQL 操作的结果都应用于数据库(提交),要么完全取消并丢弃已执行的所有 SQL 操作的结果(回滚)。在这两种情况下,数据库都保证在每个事务结束后处于一致的状态。

维护数据库一致性和数据完整性,同时又允许多个应用程序同时访问同一数据,这种特性称为并发性。在 DB2 中,并发性是通过使用隔离级别实现的。可以使用四种不同的隔离级别:

可重复的读

读稳定性

游标稳定性

未提交的读

可重复的读隔离级别可以防止所有现象,但是会大大降低并发性的程度(可以同时访问同一资源的事务数量)。未提交的读隔离级别提供了最大的并发性,但是脏读、不可重复的读和幻像都可能出现。

除了隔离级别,DB2 通过对锁的使用在多用户环境下提供并发性。锁是一种用来将数据资源与单个事务关联起来的机制,其用途是控制其他事务在资源与拥有锁的事务相关联的情况下如何与资源进行交互。可以使用几种不同类型的锁:

意向无(IN)

意向共享(IS)

下一键共享(NS)

共享(S)

意向互斥(IX)

带意向互斥的共享(SIX)

更新(U)

下一键弱互斥(NW)

互斥(X)

弱互斥(W)

超级互斥(Z)

为了维护数据完整性,DB2 数据库管理程序隐式地获取锁,获取的所有锁都在 DB2 数据库管理程序的控制之下。锁可以放置在表空间、表和行上。

为了进行优化以获取最大的并发性,行级锁通常比表级锁更好,因为它们所限制访问的资源要小得多。但是,因为所获取的每个锁都需要一定数量的存储空间和处理时间来进行管理,所以单个表级锁需要的开销比几个单独的行级锁低。

本系列其他教程推荐

DB2 9 基础(730 考试)认证指南,第 1 部分: DB2 规划 1

DB2 9 基础(730 考试)认证指南,第 1 部分: DB2 规划 2

DB2 9 基础(730 考试)认证指南,第 2 部分: 安全性

DB2 9 基础(730 考试)认证指南,第 3 部分: 访问 DB2 数据

DB2 9 基础(730 考试)认证指南,第 4 部分: 处理 DB2 数据

DB2 9 基础(730 考试)认证指南,第 5 部分: 处理 DB2 对象

DB2 9 基础(730 考试)认证指南,第 7 部分: XQuery 简介

Tags:DB 基础 考试

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接