DB2 10.1 基础认证考试 610 准备,第 6 部分: 数据并发性

本文旨在向您介绍数据一致性的概念,以及 DB2® 在单用户和多用户数据库环境中用来维护数据一致性的各种机制。本教程还将帮助您为应对 DB2 10.1 基础认证考试(考试 610)的第 6 部分而做准备。

Roger E. Sanders, 咨询公司系统工程师, EMC Corporation

Roger SandersRoger E. Sanders (roger_e_sanders@yahoo.com) 是 bC Corporation 的一位高级咨询公司系统工程师,撰写了 21 本关于 DB2 for Linux, UNIX, and Windows 的书籍,是 2011 IBM Information Champion 的获奖者。他的新书的标题是《From Idea to Print: How to Write a Technical Article or Book and Get It Published》。



2012 年 11 月 19 日

开始之前

免费下载:IBM® DB2® Express-C 10.1 免费版 或者 DB2® 10.1 for Linux®, UNIX®, and Windows® 试用版
下载更多的 IBM 软件试用版,并加入 IBM 软件下载与技术交流群组,参与在线交流。

关于本系列

正在考虑获得 IBM Certified Database Associate(DB2 10.1 基础认证)?那您就来对了地方。这个 DB2 10.1 基础认证准备系列 旨在介绍参加 DB2 10.1 基础认证考试(考试 610)之前必须了解的所有主题。即使您不打算马上参加认证考试,本系列中提供的信息也可以帮助您了解 DB2 10 for z/OS® 和 DB2 10.1 for Linux®, UNIX®, and Windows® 中的新特性和功能。

您没有看到正在寻找的教程?您可以查看 DB2 9 基础认证 730 准备系列 中的 DB2 9 教程。

关于本教程

在 DB2 10.1 基础认证考试(考试 610)中,有 13% 的内容旨在测试您对一些机制(有关 DB2 用来允许多个用户和应用程序在不影响数据一致性的情况下与数据库进行交互的机制)的了解。组成考试的这一部分的问题旨在评估:

  • 您能够识别在给定情况下应该使用的隔离级别。
  • 您能够识别 DB2 锁的特征。
  • 您能够列出可以为其获得锁的对象。
  • 您能够识别影响锁的因素。

本教程旨在介绍数据一致性的概念,以及 DB2 用于在单用户和多用户数据库环境中维护数据一致性的两个重要机制:隔离级别和锁。本教程是由 6 部分组成的系列教程中的第 6 部分,您可以使用本教程来准备 DB2 10.1 基础认证考试(考试 610)。

目标

在本教程中的材料涵盖了 DB2 10.1 基础认证考试(考试 610)的第 6 部分的目标。(您可以在 http://www-03.ibm.com/certify/tests/obj610.shtml 查看这些目标。)

在完成本教程之后,您应该能够:

  • 识别影响锁的因素。
  • 列出可以在其上获得锁的对象。
  • 适当地使用 LOCK TABLE 语句。
  • 识别 DB2 锁的特征(在所有平台上共享的常用锁)。
  • 识别在给定情况下应该使用的隔离级别。
  • 知道如何以及何时使用当前已提交 (CC) 的语义。

先决条件

要理解本教程所提供的一些内容,您应该熟悉下列术语:

  • 结构化查询语言 (Structured Query Language, SQL):一种用来在关系数据库中定义对象和操纵数据的标准化语言。
  • 对象:数据库中可以用 SQL 创建或操纵的任何东西(例如,表、视图、索引和包等)。
  • :一种逻辑结构,用来将数据表示为有固定列数的无序行的集合。每个列都包含一组值,列中所有的值都具有相同的数据类型。列的定义组成了表结构,而行包含实际的数据。
  • 记录:表中一行的存储表示。
  • 字段:表中一列的存储表示。
  • :具体的数据项,位于数据库表中行与列的每个交叉点上。
  • DB2 优化器:SQL 预编译器的一个组件,它通过对几个不同访问计划的执行成本进行建模并选择预计成本最低的计划,为数据操纵语言 (Data Manipulation Language, DML) SQL 语句选择一个访问计划。

系统需求

您不需要 DB2 的副本也可以顺利完成本教程,但是,如果您可以访问一个 DB2 数据库服务器,您就能够测试本教程所展示的一些命令和概念。

您可以从 IBM 下载 DB2 Express-C 的一个免费版本。


数据一致性

理解数据一致性

为了理解 DB2 如何尝试在单用户和多用户数据库环境中维护数据一致性,您必须先了解数据一致性是什么。此外,您还必须能够识别让数据库进入不一致状态的事件类型。那么到底什么是数据一致性?回答这个问题的最佳方法是研究示例。

假定某公司拥有多家连锁饭店,公司用一个数据库来跟踪每家饭店中的库存等信息。数据库包含每家连锁饭店的库存表。每当一家饭店收到或用掉一部分货物时,就会更新相应的库存表。现在,假定将一箱咖啡从一家饭店(其咖啡库存量充足)调配到另一家饭店(其咖啡刚刚用完)。为了准确地反映这一次库存调配,在接收方饭店的库存表中所存储的咖啡瓶数的值需要有所增加,而调出方饭店的表中所存储的咖啡瓶数的值需要有所减少。如果用户增加了接收方饭店的库存表中的咖啡瓶数的值,但没有减少调出方饭店库存表中的咖啡瓶数的值,那么数据库中的数据就会不再一致。此时所有连锁店的咖啡的总瓶数不再准确,调出方饭店的咖啡瓶数也不再准确。

在单用户环境中,如果用户没有进行必要的更改(如前面的示例),或者如果在用户或应用程序进行更改的过程中系统崩溃了,又或者如果应用程序过早地终止,那么数据库中的数据都会变得不一致。在多用户环境中,当几个用户或应用程序尝试同时访问相同的数据时,也可能会发生不一致。例如,在刚刚介绍的场景中,如果一个用户查询数据库以获得接收方饭店的咖啡瓶数,同时另一个用户在更新两家饭店的库存表,以反映有一箱咖啡从一家饭店转移到另一家饭店,在更新被提交之前,查询将会错误地显示没有剩余的咖啡。


事务、隔离级别和锁

事务

DB2 用于保持数据一致性的主要机制是事务。事务(也称为工作单元)是一种将一个或多个 SQL 操作组合成一个单元的可恢复序列,通常位于应用程序中。事务的启动和终止定义了数据库内的一致性点;要么将一个事务中执行的所有操作的结果都应用于数据库,并使其成为永久性的(已提交),要么完全撤消(已回滚)它们,并且数据库回到事务启动之前的状态。在单用户环境中,事务是按顺序运行的,并且不必与其他并行运行的事务竞争。但在多用户环境中,事务通常是同时运行的。因此,每个事务都有可能干扰正在运行的任何其他事务;要控制所允许的总干扰数量,就需要使用另一个机制:隔离级别

有可能彼此干扰的事务被称为是交错的(或并行的)事务,而彼此完全隔离的事务被称为是可序列化的 事务,这意味着,同时运行它们的结果与按顺序(一个接一个地)运行它们的结果将没有什么不同。在理想的情况下,每个事务都应该是可序列化的。为什么呢?假设一个销售人员和会计师在同一时间使用相同的数据库。现在,假设销售人员在录入 X 公司的订单(以生成报价),但没有提交其录入条目。同时,会计师将查询数据库,获得所有未支付订单的列表,检索 X 公司的新订单,并生成一个账单。现在,假设负责 X 公司的销售人员决定不下订单。因为订单还没有发出,所以该销售人员可以回滚事务,有关订单的信息会从数据库中删除。然而,一个星期后,X 公司收到的账单中包含这个从未发出的订单。如果销售人员的事务和会计师的事务彼此完全隔离(也就是说,事务是序列化的),这种情况就不会发生。无论是销售人员的事务在会计师的事务开始前完成,还是会计师的事务在销售人员的事务开始之前完成,在这两种情况下,X 公司都不会收到账单。

如果事务不是可序列化的,那么可能出现四种现象:

  • 丢失更新:当两个事务在同一时间读取相同的数据并且都试图更新该数据时,会出现这种情况,导致其中一个更新的损失。例如:事务 1 和事务 2 读取相同行的数据,并根据读出的原始值为该行计算新的值。如果事务 1 用其新值更新行,而事务 2 在之后更新同一行,那么事务 1 所执行的更新操作都将丢失。
  • 脏读 (Dirty Read):当一个事务读取尚未提交的数据时,会出现这种情况。例如:事务 1 修改了一行数据,而事务 2 在提交更改之前读取了已更改的行。如果事务 1 回滚该变更,那么事务 2 将会读取从未真正存在的数据。
  • 不可重复读取 (Nonrepeatable Read):当一个事务读取同一行数据两次,但每次都获得不同的结果时,就会出现这种情况。例如:事务 1 读取一行数据,然后事务 2 修改或删除该行并提交了变更。当事务 1 尝试重新读取该行,它会检索到不同的数据值(如果该行被更新)或发现该行已经不存在(如果该行被删除)。
  • 幻像:当一行数据与某些搜索条件相匹配,但该行在最初不可见时,就会出现这种情况。例如:事务 1 检索一组满足某些搜索条件的行,然后事务 2 插入一个新行,该行与事务 1 的查询的搜索条件相匹配。如果事务 1 再次执行生成原来那一组行的查询,被返回的将是另一组不同的行。(事务 2 所添加的新行现在将被包含在所生成的这组行中。)

隔离级别

不同的用户和应用程序可能需要同时访问或修改存储在 DB2 数据库中的数据,所以 DB2 必须能够允许多个事务同时运行,并确保数据的完整性不会受到损害。数据库资源由多个用户或应用程序同时共享,这种情况被称为并发性,而 DB2 执行并发的一种方式是使用隔离级别。顾名思义,隔离级别 确定由一个事务访问或修改的数据如何与刚好在同一时间运行的其他事务 “隔离”。DB2 10.1 识别和支持下列隔离级别:

  • 可重复读取 (Repeatable Read)
  • 读稳定性 (Read Stability)
  • 游标稳定性 (Cursor Stability)
  • 未提交的读取 (Uncommitted Read)

表 1 显示在使用每个这些隔离级别时可能出现的各种现象。

表 1. 在使用每个隔离级别时可能出现的现象
隔离级别现象
丢失更新脏读不可重复读取幻像
可重复读取
读稳定性
游标稳定性
未提交的读取

可重复读取的隔离级别

可重复读取的隔离级别是限制最严格的隔离级别。在使用它时,一个事务的影响与其他并发运行的事务的影响完全隔离。因此,丢失更新、脏读、不可重复读取和幻像都不会发生。

当使用可重复读取的隔离级别时,由拥有方事务以任何方式引用的每一行在该事务的寿命期间都被锁定。因此,如果在同一个事务中发出多次相同的查询(SELECT 语句),那么产生的结果数据集可以保证是相同的。事实上,在可重复读取隔离级别下运行的事务可以检索同一行集任意次数,并且可以对它们执行任意次数的操作,直到(由提交或回滚操作)终止事务。但是,只要事务是活动的,就不允许其他事务执行会修改拥有方事务正在访问的一行或多行的插入、更新或删除操作。

为了确保在可重复读取隔离级别下运行的事务所访问的数据不会受到其他事务的负面影响,该隔离事务所引用的每一行都被锁定,而不是只锁定实际检索或修改的那些行。因此,如果一个事务扫描 1,000 行数据是为了检索其中的 10 行,则在被扫描的所有 1,000 行数据(而不仅仅是被检索的 10 行数据)上都会获得锁,并保持锁定。

请注意:在使用可重复读取隔离级别时,如果想要通过扫描整个表或视图来响应查询,那么无论是整个表还是视图所引用的所有行都会被锁定。这大大降低了并发性,在使用大表和视图时尤其如此。

那么在现实情况中,这种隔离级别是如何工作的呢?假定您开了一家小汽车旅馆,您使用 DB2 数据库跟踪房间预订和房价信息。您还有一个基于 Web 的应用程序,它允许顾客按 “先到先服务” 的原则预订房间。如果您的预订应用程序在可重复读取隔离级别下运行,当某位顾客扫描给定日期范围内的可用房间列表时,其他顾客将无法执行在再次生成该列表时(通过执行相同查询,当然,假设是从同一个事务执行查询)会导致列表发生更改的预订或取消预订操作。同样,您的旅馆经理将无法更改在响应第一位顾客的查询时扫描的那些房间记录的房价。其他顾客 可以预订或取消预订在执行第一位顾客的查询时其记录未被扫描到的房间。同样,对于生成可用房间列表时没有被读取的任何房间记录,允许您的经理对房价进行修改。(任何人若试图预订或取消房间预订,或更改在响应第一位顾客的查询时被扫描的房间记录的房价,他们会被强制等待,直到终止第一位顾客户的事务。)图 1 说明了这种行为。

图 1. 可重复读取隔离级别如何影响应用程序行为的示例
本图显示了可重复读取隔离级别如何影响应用程序行为的示例。

读稳定性的隔离级别

读稳定性隔离级别的限制不如可重复读取隔离级别那么严格;因此,它没有将一个事务的影响与其他并发事务的影响完全隔离。在使用该隔离级别时,丢失更新、脏读和不可重复读取不会发生,但是可能会出现幻像。那是因为在使用读稳定性隔离级别时,只有隔离事务实际检索和修改的行会被锁定。因此,如果一个事务为了检索 10 行数据而扫描 1,000 行数据,那么只在被检索的 10 行(而不是所扫描的 1000 行)上提供锁,并保持锁定。由于需要较少的锁,所以可以并发运行更多事务。如果隔离事务执行多次相同的查询,那么每次产生的结果数据集可能都会有所不同。

与可重复读隔离级别一样,在读稳定性隔离级别下运行的事务可以检索一组行,并对它们执行任意次数的操作。只要该事务保持活动,就会禁止其他事务执行那些会影响该隔离事务所检索的那一组行的更新或删除操作。另一方面,允许其他事务对数据库中的任意表或可更新视图执行插入操作;如果所插入的行与隔离事务所发出的查询的选择条件相匹配,那么这些行可能作为幻像出现在后续产生的结果数据集中。

那么,该隔离级别会如何改变我们的汽车旅馆预订应用程序的工作方式呢?现在,当某位顾客通过扫描数据库来获得给定日期范围内的可用房间列表时,其他顾客将可以执行在再次生成该列表时(通过执行相同查询,假设是从同一个事务执行查询)会导致列表发生更改的预订或取消预订操作。同样,您的经理能够修改在第一位顾客的列表中没有出现的任何房间的房价。因此,每次第一位顾客生成给定日期范围内的可用房间列表时,所生成的列表都可能包含之前没有看到的房间或房价。图 2 说明了这种行为。

图 2. 读稳定性隔离级别如何影响应用程序行为的示例
本图显示了读稳定性隔离级别如何影响应用程序行为的示例。

游标稳定性隔离级别

游标稳定性隔离级别在隔离并发事务彼此间的影响方面甚至比读稳定性隔离级别还要宽松。在使用该隔离级别时,丢失更新和脏读不会发生,但有可能出现不可重复读取和幻像。这是因为在大多数情况下,在使用游标稳定性隔离级别时,只有被隔离事务引用的行被锁定。(在从结果数据集检索到记录的那一刻,指针(被称为游标)被定位于基础表中相应的行,并且该行被锁定。在游标被重新定位(往往不是由 FETCH 操作实现)时,或者在终止拥有方事务之后,该锁才会一直有效。因为只获取一个行级别的锁,所以游标稳定性隔离级别提供了最高级别的并发性。因此,这是 DB2 默认使用的隔离级别。

当一个事务使用游标稳定性隔离级别(通过游标方式)从表中检索行时,只要游标定位在某一行上,那么其他事务均不允许更新或删除该行。但是,如果被锁定的行本身不是以索引方式访问的,那么其他事务就可以将新行添加到表中,并且可以对位于游标(锁定的行)任意一侧的行执行更新和删除操作。一旦获取了锁,该锁就一直有效,直到重新定位游标或终止拥有方事务。(在大部分情况下,如果重新定位游标,则会释放当前行上的锁,并在游标被移动到的另一行上获得新锁。)如果隔离事务修改了它检索到的任何行,那么在终止拥有方事务之前(也就是说,已经移动了游标,使之离开了被修改的行),其他事务均不允许更新或删除该行。

在使用游标稳定性隔离级别时,如果同一个查询在相同事务中执行两次或两次以上,那么所产生的多个结果可能会有所不同。此外,在提交由其他事务在其他行上所做的更改之前,这些更改将是不可见的。(这对于在可重复读取隔离级别和读稳定性隔离级别下运行的事务也是一样的。)

同样,让我们探讨一下该隔离级别对我们的汽车旅馆预订应用程序的工作方式有什么影响。如果某位顾客通过扫描数据库来获得给定日期范围内的可用房间列表,然后查看列表上第一个房间的相关信息时,其他顾客可以预订或取消预订这位顾客目前正在查看的房间以外的 任何房间(适用于指定的日期范围)。同样,您的经理将能够更改除了这位顾客目前正在查看的房间以外的任何房间(同样适用于指定的日期范围)的房价。但是,这个顾客目前正在查看的房间除外。但是,对于第一个顾客目前正在查看的房间记录,您和其他顾客都不能进行任何操作。当第一位顾客查看列表中下一个房间的信息时,其他顾客和经理就可以修改第一位顾客刚才查看的房间的记录(如果这位顾客没有预订这个房间)。但任何人(不论是经理还是其他顾客)都不允许修改第一位顾客目前正在查看的房间记录。图 3 说明了这种行为。

图 3. 游标稳定性隔离级别如何影响应用程序行为的示例
本图显示了游标稳定性隔离级别如何影响应用程序行为的示例。

未提交读取隔离级别

未提交读取隔离级别是限制性最宽松的可用隔离级别。在使用该隔离级别时,一个事务的影响与其他并发事务的影响一般没有 隔离。因此,脏读、不可重复读取和幻像可能会出现,并且的确经常出现。那是因为在使用未提交读取隔离级别时,仅当一个事务试图修改它所检索的行中存储的数据时,它所检索的这些行才会被锁定。或者,如果另一个事务试图删除或更改被检索的行所在的表时,这些行也会被锁定。因为在使用未提交读取隔离级别时,行通常保持未锁定状态,所以通常在事务访问仅读表和视图,以及在被执行的事务的检索或未提交数据没有负面效果的时候,才会使用未提交读取隔离级别。

顾名思义,在提交由其他事务对行所做的更改之前,在未提交读取隔离级别下运行的事务可以看见这些更改。但是,当其他事务创建表、索引或视图,情况则有所不同。在这些情况下,必须提交创建对象的事务,然后在未提交读取隔离级别下运行的事务才可以看见或访问这些对象。这同样适用于删除了现有的表、索引或视图的情况。直到提交了删除对象的事务之后,在未提交读取隔离级别下运行的事务才会知道这些对象已不存在。(要注意一个要点是,当在该隔离级别下运行的事务使用可更新游标时,该事务的行为就像它在游标稳定性隔离级别下运行一样,并且游标稳定性隔离级别的约束也适用。)

那么未提交读取隔离级别对我们的汽车旅馆预订应用程序有什么影响呢?现在,如果某位顾客通过扫描数据库来获得给定日期范围内的可用房间列表,那么其他顾客也可以预订或取消预订旅馆中的任何房间,包括第一位顾客目前正在查看的房间。同样,您的经理将可以修改旅馆中任何房间(在任何日期范围内)的房价。(不幸的是,第一位顾客可能会被阻止预订一间看起来已被占用但实际上可用的房间。或者,他们可能以某个房价预订了房间,但却发现房价已经更改了。)图 4 说明了这种行为。

图 4. 未提交读取隔离级别如何影响应用程序行为的示例
本图显示了未提交读取隔离级别如何影响应用程序行为的示例

指定要使用的隔离级别

尽管隔离级别控制事务层的并发性,但实际上它们是在应用程序层(或者在某些情况下,在 SQL 语句层)指定的。因此,在大部分情况下,由特定应用程序使用的隔离级别会被应用到由该应用程序执行的每一个事务。(要注意的一个要点是,一个应用程序可能由多个部分构造而成,每个部分可能都被分配一个不同的隔离级别,在这种情况下,由某个部分所使用的隔离级别来决定在该部分内的每个事务将使用的隔离级别。)

对于嵌入式 SQL 应用程序,要使用的隔离级别是在预编译时或在将应用程序被绑定到数据库(如果使用延迟绑定)时指定的。在这种情况下,可以使用 PRECOMPILEBIND 命令的 ISOLATION [RR | RS | CS | UR] 选项来设置隔离级别。

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

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

前面曾提到过,当没有(使用上述方法之一)显式设置特定应用程序的隔离级别时,会默认使用游标稳定性隔离级别。这适用于从 DB2 Command Line Processor (CLP) 执行的 DB2 命令、SQL 语句和脚本,以及嵌入式 SQL、CLI/ODBC、JDBC 和 SQLJ 应用程序。因此,正如可以控制应用程序所使用的隔离级别一样,您也可以控制从 DB2 Command Line Processor 执行的操作时使用的隔离级别。在这种情况下,可以通过在建立数据库连接之前执行 CHANGE ISOLATION LEVEL 命令来设置隔离级别。

在 DB2 8.1 及其高版本中,在执行独立查询时,可以覆盖默认隔离级别(或为特定应用程序指定隔离级别)。要做到这一点,请将 WITH [RR | RS | CS | UR] 子句追加到 SELECT 语句 — 该子句表示,使用可重复读取 (RR)、读稳定性 (RS)、游标稳定性 (CS) 或未提交的读取 (UR) 隔离级别来执行 SELECT 语句。因此,如果您想获得在特定部门中工作的所有员工的列表,并且希望在可重复读取隔离级别下运行产生该列表的查询,那么只需要执行一个 SELECT 语句即可,示例如下:

SELECT lastname FROM employee WHERE workdept = 'E11' 
   WITH RR

如果您的应用程序在大多数时候需要比较宽松的隔离级别(以支持最大的并发性),但是它所包含的某些查询必须防止某些类型的现象(脏读、不可重复读取和/或幻像)出现,那么您可以综合使用应用程序层和 SQL 语句层的隔离级别来实现您的目标。

这些隔离级别有一个共同点,那就是它们都获得一个或多个锁。但是 究竟是什么呢?锁是一种用来将数据资源与单个事务关联起来的机制,其用途是在某个资源与锁定它的事务有关联时控制其他事务与该资源进行交互的方式。(与某数据资源关联的事务被称为 “持有” 或 “拥有” 该锁。)基本上,数据库环境中的锁的用途与房屋或汽车中的锁相同:在这种情况下,它们决定谁可以或不可以获得对特定资源的访问,这些资源可能是一个或多个表空间、表或行。DB2 利用锁来禁止其他事务执行可能对 “拥有方” 事务有负面影响的数据修改。当拥有方事务被(提交或回滚操作)终止时,对资源所进行的更改将变成永久性的更改或被撤消,而在资源上代表拥有方事务获取的所有锁将被释放。一旦解锁,另一个活动事务就可以锁定和操纵资源。图 5 说明了事务/资源锁定的原理。

图 5. DB2 如何通过使用锁阻止不受控制的并发资源访问
本图显示了 DB2 如何通过使用锁阻止不受控制的并发资源访问

锁的属性和状态

DB2 使用的锁均具有以下基本属性:

  • Object:标识被锁定的数据资源。DB2 在需要时在数据资源(特别是,表空间、表和行)上隐式地获取锁。
  • Size:标识被锁定的数据资源的物理大小。(换言之,标识被锁定数据的数量。)锁并不总是必须控制整个数据资源。例如,DB2 可以选择让应用程序以独占方式控制表中的一个或两个特定的行,而不是让事务以独占方式控制整个表。
  • Duration:标识持有锁的时间长度。所使用的隔离级别通常对锁的持续时间有重大影响。例如,对于要访问 500 行的一个可重复读取事务所获取的锁,在 500 行都要被更新的情况下,该锁可能有较长的持续时间。然而,对于游标稳定性事务所获取的锁,其持续时间可能会短得多。
  • State(或 Mode):标识允许锁的拥有者和锁定数据资源的其他并发用户使用的访问类型。表 2 显示了 DB2 10.1 中各种可用的锁状态(以及其影响),以增加对资源的控。
表 2. DB2 10.1 中可用的锁状态(模式)
锁状态(模式)平台适用对象锁的拥有者访问并发事务访问
无意向 (Intent None, IN)DB2 for Linux, UNIX, and Windows表空间、块、表和数据分区锁的拥有者可以读取被锁定表中的所有数据(包括未提交的数据),但锁的拥有者不能更改存储在资源中的数据。无意修改数据的只读事务通常会获取无意向锁(因此,不会代表事务获取其他锁)。其他事务可以读取和更改在锁定资源中存储的数据,但它们不能删除在资源中存储的数据。
共享意向 (Intent Share, IS)DB2 for Linux, UNIX, and Windows; DB2 for z/OS表空间、块、表和数据分区锁的拥有者可以读取存储在锁定资源中的所有数据(不包括未提交的数据),但锁的拥有者不能更改存储在资源中的数据。若事务没有表达要更改数据的意图(也就是说,事务中没有包含 SELECT FOR UPDATEUPDATE WHEREINSERT 语句),那么该事务一般会获取共享意向锁。其他事务可以读取和更改存储在锁定资源中的数据。
互斥意向 (Intent Exclusive, IX)DB2 for Linux, UNIX, and Windows; DB2 for z/OS表空间、块、表和数据分区锁的拥有者可以读取和更改存储在锁定资源中的数据。若事务表达了要更改数据的意图(也就是说,事务中包含 SELECT FOR UPDATEUPDATE WHEREINSERT 语句),那么该事务一般会获取互斥意向锁。其他事务可以读取和更改存储在锁定资源中的数据。
扫描共享 (Scan Share, NS)DB2 for Linux, UNIX, and Windows锁的拥有者可以读取存储在锁定资源中的所有数据(不包括未提交的数据),但锁的拥有者不能更改存储在资源中的数据。在读稳定性 (RS) 或游标稳定性 (CS) 隔离级别下运行的事务一般会获取下一键共享 (Next Key Share) 锁,而不是共享 (S) 锁。其他事务可以读取存储在锁定资源中的所有数据(不包括未提交的数据),但它们不能更改存储在资源中的数据。
下一键弱互斥 (Next Key Weak Exclusive, NW)DB2 for Linux, UNIX, and Windows锁的拥有者可以读取存储在锁定资源中的所有数据(不包括未提交的数据),但锁的拥有者不能更改存储在资源中的数据。 每当将一个行插入某个索引时,在表中的下一个可用行上一般会获取下一键弱互斥锁。仅当下一行目前由在可重复读取 (RR) 隔离级别下执行的扫描锁定时,才会出现这种情况。其他事务可以读取存储在锁定资源中的所有数据(不包括未提交的数据),但它们不能更改存储在资源中的数据。
共享 (Share, S)DB2 for Linux, UNIX, and Windows; DB2 for z/OS块、表、行和数据分区锁的拥有者可以读取存储在锁定资源中的所有数据(不包括未提交的数据),但锁的拥有者不能更改存储在资源中的数据。如果在可重复读 (RR) 隔离级别下运行的事务没有表达修改数据的意图,那么该事务一般会获取共享锁。(包含 SELECT FOR UPDATEUPDATE WHEREINSERT 语句的事务表达修改数据的意图。)其他事务可以读取存储在锁定资源中的所有数据(不包括未提交的数据),但它们不能更改存储在资源中的数据。
带互斥意向的共享 (Share With Intent Exclusive, SIX)DB2 for Linux, UNIX, and Windows; DB2 for z/OS块、表和数据分区锁的拥有者可以读取和更改存储在锁定资源中的数据。如果在一个资源上持有共享 (S) 锁的事务尝试在同一个资源上获取互斥意向 (IX) 锁,那么该事务一般会获得带互斥意向的共享(反之亦然)。其他事务可以读取存储在锁定资源中的所有数据(不包括未提交的数据),但它们不能更改存储在资源中的数据。
更新 (Update, U)DB2 for Linux, UNIX, and Windows; DB2 for z/OS块、表、行和数据分区锁的拥有者可以更改存储在锁定资源中的所有数据(不包括未提交的数据),但锁的拥有者不能读取存储在资源中的数据。使用 INSERTUPDATEDELETE 语句更改数据的事务一般会获取更新锁。其他事务可以读取存储在锁定资源中的所有数据(不包括未提交的数据),但它们不能更改存储在资源中的数据。
互斥 (Exclusive, X)DB2 for Linux, UNIX, and Windows; DB2 for z/OS块、表、行、数据分区和缓冲池锁的拥有者可以读取和更改存储在锁定资源中的数据。如果事务使用 SELECT 语句检索数据,然后使用 INSERTUPDATEDELETE 语句更改数据,那么该事务一般会获取互斥锁(只使用 SELECT 语句检索数据的事务不会获取互斥锁。)使用未提交读取隔离级别的事务可以读取存储在锁定资源中的所有数据(不包括未提交的数据),但它们不能更改存储在资源中的数据。所有其他事务均不能读取或更改存储在锁定资源中的数据。
超级互斥 (Super Exclusive, Z)DB2 for Linux, UNIX, and Windows表空间、块、表和数据分区锁的拥有者可以读取和更改存储在锁定资源中的数据。如果锁的拥有者试图更改表、删除表、为表创建索引、删除已经为表定义的索引,或者运行 REORG 实用程序重组表的内容(当表处于离线状态时),那么一般会在该表上获取超级互斥锁。其他事务均不能读取或更改存储在锁定资源中的数据。
摘自 IBM DB2 10.1 Information Center for Linux, UNIX, and Windows 中 Lock Attributes 下的表 1。 (http://publib.boulder.ibm.com/infocenter/db2luw/v10r1/topic/com.ibm.db2.luw.admin.perf.doc/doc/c0005270.html)

如何获取锁

除了使用未提交读取隔离级别的情况外,事务从不需要显式地请求锁。因为 DB2 在需要锁时会自动获取锁。一旦获取了锁,这些锁在被释放之前都会一直受 DB2 的控制。默认情况下,DB2 始终尝试获取行级别的锁。但是,可以通过执行特殊形式的 ALTER TABLE SQL 语句,控制试图在特定表资源上获取行级锁还是表级锁。该形式的 ALTER TABLE 语句的语法是 ALTER TABLE [TableName] LOCKSIZE [ROW | TABLE],其中 TableName 标识现有表的名称,为其指定所有事务在访问该表时将要使用的锁定级别。

请注意:这种形式的 ALTER TABLE 语句只可用于 DB2 for Linux, UNIX, and Windows,不能用于 DB2 for z/OS。

例如,如果执行 SQL 语句 ALTER TABLE employee LOCKSIZE ROW,DB2 会自动为访问名称是 EMPLOYEE 的表的每个事务获取行级别的锁。(这是默认行为。)如果改为执行 SQL 语句 ALTER TABLE employee LOCKSIZE TABLE,DB2 会尝试为访问 EMPLOYEE 表的每个事务获取表级别的锁。

但是,如果您不希望使用某个特定表的每个事务都获得表级锁呢?相反,如果您希望有一个或两个特定事务获取表级锁,而其他所有事务在使用该表时都获取行级锁,又该如何做呢?在这种情况下,您只需要保留默认锁定行为(那么就会使用行级锁定),并使用 LOCK TABLE 语句为所需的个别事务获取表级锁。LOCK TABLE 语句的语法是 LOCK TABLE [TableName] IN [SHARE | EXCLUSIVE] MODE,其中 TableName 按名称标识将要锁定的表。

正如您可以看到的,LOCK TABLE 语句允许事务采用以下两种模式之一在特定的表上获取表级锁:SHAREEXCLUSIVE。如果表被锁定为 SHARE 模式,则会代表请求事务获取表级共享 (S) 锁,并允许其他并发事务读取存储在锁定表中的数据,但不允许对这些数据进行更改。另一方面,如果表被锁定为 EXCLUSIVE 模式,则会获取表级互斥 (X) 锁,而其他并发事务无法对锁定的表执行任何类型的操作。

例如,如果执行 LOCK TABLE employee IN SHARE MODE SQL 语句,则在名称为 EMPLOYEE 的表上代表当前事务获取表级别的共享 (S) 锁(假定没有其他事务在该表上持有锁),并允许其他任何并发运行的事务读取存储在 EMPLOYEE 表中的数据,但不允许对其进行更改。另一方面,如果改为执行 SQL 语句 LOCK TABLE employee IN EXCLUSIVE MODE,那么 EMPLOYEE 表将会获取表级互斥 (X) 锁,并且不允许其他事务读取或修改存储在该表中的数据,直到提交或回滚执行 LOCK TABLE 语句的事务。

在涉及决定使用行级锁还是表级锁时,重要的是要记住,在任何时候,如果某个事务在一个特定的资源上持有锁,那么可能会拒绝其他事务访问该资源,直到终止拥有方事务。因此,行级锁通常优于表级锁,因为它们限制访问的资源要少得多。但是,由于每个已获取的锁都需要一定数量的存储空间(用于持有锁)和一定程度的处理时间(用于管理锁),所以在使用单表级锁而不是使用多个行级锁时,通常开销会少得多。

在一定程度上,可以使用 LOCK TABLE 语句(以及使用 DB2 for Linux, UNIX, and Windows 时用到的 ALTER TABLE 语句)在全局级别 (ALTER TABLE) 和事务级别 (LOCK TABLE) 上控制锁的粒度(也就是说,决定使用行级锁还是表级锁)。那么,在全局级别比在事务级控制粒度更为理想吗?一切都要视情况而定。

假设您有一个只读的查找表,多个并发事务需要访问它。强制 DB2 在全局代表试图访问该表的每个事务上获取表级共享 (S) 锁,这可能会提高性能,因为所需的锁定开销将会大大降低。另一方面,如果您有一个只读事务需要频繁访问的表,并且该表被执行某些类型的维护的单个事务定期访问,那么可以强制 DB2 只为维护事务获取一个表级互斥 (X) 锁,这可能比强制 DB2 为试图访问该表的每个事务获取表级互斥 (X) 锁更为合理。在这种情况下,如果在实例级别上获取表级互斥 (X) 锁,只读事务只在维护事务运行时被锁在表外;在其他所有情况下,这些事务都可以并发地访问该表,不需要大量的锁定开销。


锁避免和当前已提交的语义

锁避免

在 DB2 9.7 之前,如果使用游标稳定性隔离级别,并代表一个事务锁定了一个行,那么 DB2 会阻止其他并发运行的事务试图修改被锁定的行。此外,如果持有锁的事务以任何方式修改被锁定的行,那么其他并发事务中的 SQL 语句不允许访问该行(除非它们在未提交读取隔离级别下运行),直到终止事务。(换句话说,写入程序会阻塞读取程序,而在某些情况下,读取程序也可以阻塞写入程序。)在这两种情况下,需要访问锁定的行的并发事务都被迫等待锁被释放,然后他们才可以继续处理其事务。这反过来又往往会导致意外行为的发生。

在 DB2 9.5 中,引入了一些锁避免技术,帮助消除游标稳定性隔离级别所需的一些锁定开销。实际上,在知道被访问的数据和/或页面已经提交时,这些技术允许在不锁定行的情况下执行扫描操作。例如,请查看下面的查询:SELECT COUNT(*) FROM sales

在 DB2 9.5 之前,在执行这样的查询时,所指定的表中的第一行会被锁定,计数会开始,锁会被释放。然后在表中的第二行将被锁定时,计数将被更新,并且锁将被释放。而这将继续下去,直到表中的所有行都被计数。在 DB2 9.5 及其更高版本中,同样的查询将扫描指定的表并对行进行计数,但不会再获取和释放间歇性锁,而是假定 DB2 不必获取锁就可以确定该行已被提交。实质上,锁避免可以让 DB2 确定所需的数据是否已提交,如果的确已提交,则无需再获取锁。在 DB2 9.7 和 10.1 中,锁避免对在游标稳定性隔离级别下使用游标阻塞执行的任何只读 SQL 语句很有效。(游标阻塞是一项技术,通过让 DB2 在一个操作中检索行块(而不是单一的行)来降低开销)。

当前已提交的语义

在 DB2 9.7 中,提供了一个新的游标稳定性隔离级别实现,它结合了当前已提交 (CC) 的语义,以便进一步防止写入程序阻塞读取程序。其目的是提供一个游标稳定性隔离级别,在不违反游标稳定性隔离级别语义的 ANSI 标准的情况下避免锁等待。(在 DB2 for Linux,UNIX and Windows 的早期版本中,可以使用下面的注册表变量在某些情况下推迟或避免获取锁:

  • DB2_SKIPINSERTED:允许进行游标稳定性/读稳定性扫描,以跳过未提交的插入行。
  • DB2_SKIPDELETED:允许进行游标稳定性/读稳定性扫描,以跳过未提交的删除行和索引键。
  • DB2_EVALUNCOMMITTED:允许进行游标稳定性/读取稳定性扫描,对未提交的数据应用和执行查询谓词评估;还允许扫描跳过未删除的行。实际上,扫描被视为未提交的读操作,直至找到符合条件的行,此时 DB2 可能需要获取一个锁,以确保只有提交的数据被处理或返回。

但使用这些注册表变量会导致违反游标稳定性隔离级别的语义的 ANSI 标准)。

使用在 DB2 9.5 中引入的锁避免技术,只要 DB2 判断出所需的数据已提交,那么在当前已提交语义下操作的只读事务就不会获取锁。(执行读取和写入操作的事务将会避免未提交的插入操作上的锁等待,而执行只读操作的事务在遇到来自并发事务的未提交的更新/删除操作时,最终会使用锁等待换取日志读取)。如果 DB2 无法确定行是否已被提交,那么它会尝试在有疑问的行上代表事务获取锁,如果可以获取锁,那么它会使用传统的游标稳定性隔离级别行为继续执行相关处理。如果无法获取锁(因为另一个事务在该行上持有互斥锁),那么 DB2 会检查由另一个事务持有的锁,以获得有关包含所需数据的行的信息。每个锁可以包含一个(并且只是一个)下列信息:

  • 没有信息:表示该行被锁定,但没有对它进行任何操作(在动态中没有未提交的更改)。
  • 一个未提交的插入标识符:表示该行是尚未提交的新插入的行。
  • 日志信息:表示该行包含未提交的数据。在这种情况下,日志信息识别当前在该行上持有锁的事务第一次修改该行的相应日志记录。

如果锁不包含任何信息,则按照该行已获取所需的锁的方式处理它。如果锁包含一个未提交的插入标识符,则跳过该行,因为此标识符表示尚未提交的行。如果锁包含日志信息,那么可以使用这些信息从存储在日志缓冲区或事务日志文件中的日志记录中返回行的当前已提交版本(也就是说,这个行在变更开始前就已经存在)。(DB2 使用的日志序列号 (Log Sequence Number, LSN) 直接访问相应的日志记录,参见侧栏)。

DB2 如何判断数据是否已提交

所有数据行和索引条目都有一个 “标志” 字节,其中包含一个 "Possibly UNCommitted" (PUNC) 位。如果未设置 PUNC 位,可以确定数据行/索引条目已提交,否则提交状态是未知的。

页面中包含一个 "pageLSN" ,它识别了与页面的最后一次修改对应的日志记录的 LSN。如果 pageLSN 比数据库的 commitLSN 或表的 readLSN 更早,那么可以确定行/键已经提交,否则提交状态是未知的。

要注意的要点是,当前已提交的语义可能适用于在读稳定性 (RS) 和游标稳定性隔离级别下执行的 SQL 语句。在读稳定性隔离级别下,当前已提交语义只提供 DB2_SKIPINSERTED 行为,该功能使得未插入的行不再需要等待锁释放。

图 6 介绍了运行在游标稳定性隔离级别上并且启用了目前已提交语义的 SELECT 语句,说明了该语句如何在另一个事务对记录进行更改的同时检索记录。在这个示例中,事务 1 执行三个 DML 语句,导致日志信息被写入到日志缓冲区,还有一个未提交的插入标识符被写入 SALES_REP 表的锁列表。当事务 2 查询 SALES_REP 表时,目前已提交语义允许从包含先前提交的事务的有关信息的日志记录中读取锁定的行的数据;查询不会返回未提交的插入的记录。

图 6. 在游标稳定性隔离级别下运行并且启用了目前已提交语义的查询如何检索记录的示例
本图显示在游标稳定性隔离级别下运行并且启用了目前已提交语义的查询如何检索记录的示例

启用目前已提交语义行为

在使用 DB2 9.7 及其更高版本创建的新数据库中,默认情况下会启用当前已提交语义。将现有的数据库升级到 DB2 9.7 或更高版本,这样也可以充分利用当前已提交的语义,只需将值 ON 或值 AVAILABLE 分配给转换后的数据库的 cur_commit。如果将 cur_commit 数据库配置文件参数设置为 ON,那么当前已提交语义会在数据库范围内应用于读稳定性游标稳定性隔离级别。如果将 cur_commit 数据库配置参数设置为 AVAILABLE,那么 DB2 会将相应的信息存储在锁中,并执行所需的额外日志开销(以确保所记录的数据包含被更改的行的完整未提交版本),以支持当前已提交语义。必须逐个应用程序启用当前已提交语义行为。为此,需要将嵌入式 SQL 应用程序绑定到使用 CONCURRENTACCESSRESOLUTION USE CURRENTLY COMMITTED 选项的数据库,或通过 CLI/ODBC 和 Java 应用程序指定 SQL_ATTR_CONCURRENT_ACCESS_RESOLUTION 连接属性。

需要重点注意的是,当前已提交语义的使用会导致对已被定义为 DATA CAPTURE NONE 的表进行更新操作时需要更多的日志空间。这个额外空间用于日志事务对数据行的第一个更新;该数据将用于检索行的当前已提交映像。


锁和性能

由于 DB2 在需要锁的时候隐式地获取锁,所以,除了使用 LOCK TABLE 语句(对于 DB2 for Linux, UNIX, and Windows,则使用 ALTER TABLE 语句)强制 DB2 获取表级锁之外,锁定基本上不受您的控制。有几个因素会影响锁定如何影响性能。这些因素包括:

  • 锁兼容性
  • 锁转换
  • 锁升级
  • 锁等待和超时
  • 死锁

知道这些因素是什么,并了解它们如何影响性能,这可以帮助您设计在多用户数据环境下良好运作的数据库应用程序。

锁兼容性

如果某个事务放置在数据资源上的锁的状态是:在第一个锁被释放之前,另一个事务可以在同一资源上放置另一个锁,那么就认为这两种锁是兼容的。每当一个事务在数据资源上持有一个锁,而第二个事务试图在同一资源上获取锁时,DB2 会检查每个锁的状态,判断它们是否兼容。表 3 包含的锁兼容性矩阵标识了哪些锁是兼容的。

表 3. 锁兼容性矩阵
第二个事务请求的锁
锁状态INISNSSIXSIXUXZNW
第一个事务持有的锁IN
IS
NS
S
IX
SIX
U
X
Z
NW
是 — 锁是兼容的。锁请求被立即获准。 否 — 锁不兼容。请求事务必须等待,直到持有的锁被释放,或发生锁超时,然后锁请求才可以被同意。
锁状态: IN — 无意向 IS — 共享意向 NS — 扫描共享 S — 共享 IX — 互斥意向 SIX — 带互斥意向的共享 U — 更新 X — 互斥 Z — 超级互斥 NW — 下一键弱互斥
摘自 IBM DB2 10.1 Information Center for Linux, UNIX, and Windows 中 Lock type compatibility 下的表 1。 (http://publib.boulder.ibm.com/infocenter/db2luw/v10r1/topic/com.ibm.db2.luw.admin.perf.doc/doc/r0005274.html)

锁转换/提升

如果在资源上持有锁的事务需要在该资源上获取限制更严格的锁,那么 DB2 不会释放旧锁再获取新锁,而是尝试将所持有的锁的状态更改成所需的限制更严格的状态。更改现有锁的状态的操作被称为锁转换 (DB2 for Linux, UNIX, and Windows) 或锁提升 (DB2 for z/OS);发生锁转换/提升是因为一个事务在任意给定资源上只允许持有一个锁。图 7 说明了锁转换/提升的工作方式。

图 7. 锁转换/提升更改已持有的锁
本图显示了锁转换/提升更改已持有的锁

在大多数情况下,锁转换/提升在行级锁上执行,其过程相当简单。例如,如果持有更新 (U) 锁,但是需要互斥 (X) 锁,那么更新 (U) 锁将会转换/提升为互斥 (X) 锁,在涉及共享 (S) 锁和互斥意向 (IX) 时,则并非总是如此。因为无法确定其中哪个锁更严格,如果持有这两种锁的其中一种,但请求了另一种锁,那么所持有的锁将会转换成带互斥意向的共享 (SIX) 锁。对于其他所有锁而言,假定所请求的锁状态是更严格的状态,那么当前锁的状态将会转换为所请求的锁状态。(仅当所持有的锁可以增加其严格性时,才会发生锁转换/提升。)在转换了锁状态之后,锁会保持所获取的最高级别状态,直到终止持有该锁的事务,锁才会被释放。

锁升级

在第一次建立数据库连接时,会保留特定的内存量,以保存 DB2 用于管理锁的结构。这种结构被称为锁列表,是获取每个活动事务所持有的锁后存储它们的位置。(为锁列表保留的实际内存量通过 locklist 数据库配置参数进行控制)。

因为可用内存量是有限的,并且因为该内存必须被所有活动事务共享,所以 DB2 会在锁列表中限制每个事务允许使用的空间量(该限制通过 maxlocks 数据库配置参数进行控制)。为了防止数据库代理(代表事务工作)超过其锁列表空间限制,当代表单个事务所获取的锁(与其类型无关)过多时,就会自动执行被称为锁升级 的进程。在锁升级过程中,通过用单一表级锁替换若干个行级锁,从而释放锁列表中的空间。图 8 说明了锁升级的工作方式。

图 8. 锁升级用单一表级锁替换若干个独立的行级锁
本图显示了锁升级如何用单一表级锁替换若干个独立的行级锁

那么,锁升级到底如何工作呢?当事务请求锁而数据库的锁列表已满时,与请求锁的事务有关联的其中一个表被选中,代表事务获取一个表级锁,并且释放该表的所有行级锁,从而在锁列表中让出空间。然后将表级锁添加到锁列表,如果锁列表获得的存储空间仍然不足以获取所请求的锁,则选中另一个表,重复这个过程,直到有足够的可用空间,只有在这个时候,才会获取所请求的锁(这时,事务将被允许继续)。如果在已经升级了该事务的所有行级锁之后,仍然没有获得所需的锁列表空间,则会生成一个错误,事务对数据库所做的所有更改都将被回滚,然后事务被正常终止。

请注意:使用 LOCK TABLE 语句并不会阻止正常的锁升级发生,但它可能会降低锁升级发生的频率。

锁等待和超时

如我们所见,每当一个事务在特定数据资源上持有锁时,在持有锁的事务终止(在这种情况下,代表所获取的所有锁都被释放)之前,其他并发运行的事务对该资源的访问可能都会遭到拒绝。因此,如果没有某种锁超时机制,那么事务可能需要无限期地等待另一个事务所持有的锁被释放。不幸的是,如果某个事务被另一个用户或应用程序提前终止,数据一致性可能会遭到破坏。

为了避免发生这种情况,DB2 中引入了一个重要的特性,即锁超时检测。在使用该特性时,它可以防止事务无限期地等待锁被释放。在适当的数据库配置文件中,通过将一个值分配给 locktimeout 参数,就可以控制何时发生锁超时检测。该参数指定了任何事务为了获取请求的锁而需要等待的时间长度;如果在指定的时间间隔过去之后仍未获得想要的锁,那么该事务会回滚对数据库所做的所有更改,事务被正常终止。

请注意:默认情况下,会将 locktimeout 配置参数设置为 -1,这意味着事务将无限期地等待获取它们所需要的锁。在许多情况下,该值应被修改为默认值之外的其他值。此外,应该编写一些应用程序,通过它们捕获 DB2 返回的任何超时(或死锁)SQL 返回代码,并作出适当的响应。

死锁

在许多情况下,要避免事务无限期地等待锁,可以使用当前已提交的语义,并指定锁超时。但是,当锁争用导致死锁 现象时,则不能采用该方法。要说明死锁如何发生,最佳方式是采用一些示例:假定事务 1 在表 A 上获取了互斥 (X) 锁,而事务 2 在表 B 上获取了互斥 (X) 锁。现在,假定事务 1 尝试在表 B 上获取互斥 (X) 锁,而事务 2 尝试在表 A 上获取互斥 (X) 锁。我们已经看到,这两个事务的处理都将被挂起,直到它们的第二个锁请求被同意。因为在任何一个拥有方事务释放它目前持有的锁(通过执行一个提交或回滚操作)之前,这两个锁请求都不会获得批准,并且这两个事务都由于正在等待获取锁而不能执行提交或回滚操作,所以发生了死锁的情况。图 9 说明了这个场景。

图 9. 死锁
本图显示死锁的情况

将死锁称为死锁循环 将更为准确,因为涉及的事务形成了一个等待状态的循环。该循环中的每一个事务都在等待循环中的另一个事务所持有的锁被释放(参见图 9)。在发生死锁循环时,所有涉及的事务都将无限期地等待某个锁被释放,除非某个外部代理介入并打破循环。在 DB2 中,这个代理是一个后台进程,被称为死锁检测器,它惟一的责任就是定位并解决锁定子系统中的任何死锁。

每个数据库都有自己的死锁检测器,激活它是数据库初始化过程的一部分。在激活死锁检测器后,它大部分时间保持 “睡眠” 状态,但会在预设的时间间隔醒来,并检查锁定子系统,以确定是否存在死锁的情况。通常情况下,如果死锁检测器醒来,发现锁定子系统中没有死锁,那么它会再次进入睡眠状态。如果死锁检测发现了一个死锁循环,那么它会随机选择其中一个所涉及的事务,回滚并终止该事务;然后,被选中的事务(被称为受害者进程)会发送一个 SQL 错误代码,并释放它已获取的每个锁。然后,其余的事务就可以继续,因为死锁循环已经被打破。在一个数据库的锁定子系统中有可能存在多个死锁循环,但这种可能性不大。如果存在若干个死锁循环,检测器会定位每一个死锁循环,以相同的方式终止其中一个违规事务,直到所有的死锁循环都被打破。最终,死锁检测器会再次进入睡眠,在下一个预定的时间间隔醒来,并再次检查锁定子系统。

虽然大多数死锁循环涉及两个或两个以上的资源,但有一种被称为转换死锁 的特殊死锁类型,可能会在一个单独的资源中发生。当两个或两个以上的事务已经在一个对象上持有兼容的锁,并在同一个对象上请求不兼容的新锁时,就会发生转换死锁。如果两个或两个以上并发事务通过执行索引扫描在表中搜索行,然后尝试修改一个或多个检索到的行,通常会出现这种情况。


结束语

如果用户忘记了进行所有必要的更改,或者在用户进行更改的过程中系统崩溃了,抑或数据库应用程序过早地停止了,那么数据库就会变得不一致。当若干个用户同时访问相同的数据库资源时,也有可能出现不一致的情况。例如,一个用户可能在所有表获得适当更新之前读取了另一个用户的更改,并根据所读取的过早的数据值执行了一些不适当的操作。为了防止数据不一致(尤其是在多用户环境中),DB2 的设计中融入了下列数据一致性支持机制:

  • 事务
  • 隔离级别

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

隔离级别确定了由一个事务访问和/或修改的数据如何与刚好在同一时间运行的其他事务 “隔离”。DB2 识别和支持下列隔离级别:

  • 可重复读取
  • 读稳定性
  • 游标稳定性
  • 未提交的读取

可重复读取隔离级别是限制最严格的可用隔离级别,但是会大大降低并发性(可以同时访问同一资源的事务数量)。另一方面,未提交读取隔离级别提供了最大的并发性,但是脏读、不可重复读取和幻像都可能出现。

锁是一种用来将数据资源与单个事务关联起来的机制,其用途是控制其他事务在资源与获取锁的事务有关联的情况下如何与资源进行交互。在 DB2 中,可以使用以下类型的锁:

  • 无意向 (IN)
  • 共享意向 (IS)
  • 互斥意向 (IX)
  • 扫描共享 (NS)
  • 下一键弱互斥 (NW)
  • 共享 (S)
  • 带互斥意向的共享 (SIX)
  • 更新 (U)
  • 互斥 (X)
  • 超级互斥 (Z)

为了维护数据完整性,DB2 隐式地获取锁,获取的所有锁都受 DB2 Database Manager 的控制。可以将锁放置在表空间、块、表和行上。为了进行优化以获取最大的并发性,行级锁通常比表级锁更好,因为它们限制访问的资源要少得多。但是,因为所获取的每个锁都需要一定数量的存储空间和处理时间来进行管理,所以获取和维护单个表级锁需要的开销比几个单独的行级锁低。

本教程旨在向您介绍数据一致性的概念,以及 DB2 在单用户和多用户环境下维护数据库一致性时所使用的各种机制。本文还想帮助您准备 DB2 10.1 基础认证考试(考试 610)。您现在应该已经更好地理解了数据一致性,并且能够:

  • 识别影响锁的因素
  • 列出可以在其上获得锁的对象
  • 适当地使用 LOCK TABLE 语句
  • 识别 DB2 锁的特征
  • 识别在给定情况中应该使用的隔离级别
  • 知道如何以及何时使用当前已提交 (CC) 的语义

参考资料

学习

获得产品和技术

  • 利用可从 developerWorks 直接下载的 评估 IBM 产品试用版软件 构建您的下一个开发项目。
  • 现在您可以免费使用 DB2。下载 IBM 软件下载:IBM DB2 Express-C 10.1,这是面向社区的免费版本的 DB2 Express Edition,提供与 DB2 Express Edition 相同的核心数据特性,并为构建和部署应用程序提供了一个坚实的基础。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management
ArticleID=846289
ArticleTitle=DB2 10.1 基础认证考试 610 准备,第 6 部分: 数据并发性
publish-date=11192012