[转]DB2 并发机制表格分析法

级别: 中级java

齐克科 (qikeke@cn.ibm.com), 软件工程师, IBM
Elaine Zhan (ezhan@cn.ibm.com), 软件咨询工程师, IBM
陈荔龙 (chenlil@cn.ibm.com), 软件工程师, IBM数据库

2008 年 5 月 08 日编程

本文介绍一种数据库并发机制表格分析方法。借助表格,可以帮助理解、分析 DB2 的并发机制,并能对 DB2如何实现这种并发机制窥探一二。咱们还将借助一些实例,来进行概念的验证和对理论更加深入的理解。

基本概念安全

对于数据库初学者来讲,深入理解 DB2 里面的并发机制并非一件很容易的事。事务、隔离级别、锁、锁兼容这些概念读起来十分晦涩,这些概念之间的关系彷佛也是若隐若现,使人琢磨不透。服务器

这里咱们无心把全部的数据库并发机制相关概念再复述一遍,这不是本文的重点,可是颇有必要在这里对一些重要的概念稍微复习一下,包括事务,隔离级别,锁,锁兼容等。对这些概念有大概的了解会对您阅读本文大有帮助。并发

事务jsp

事 务在 DB2 中一般称为工做单元。工做单元是应用程序进程内可恢复的操做序列。数据库管理器使用它来确保数据库处于一致状态。对数据库的任何读取或写入都在一个工做单 元内执行。当对数据库发出第一条 SQL 语句时,就隐式启动了一个工做单元。由同一应用程序执行的全部后续读写操做被认为是同一工做单元的一部分,直到应用程序发出 COMMIT 或 ROLLBACK 语句来结束该工做单元。性能

隔离级别学习

隔离级别肯定访问数据时如何锁定或者隔离该数据,以使其不受其余进程的影响。除非显式地进行更改,不然隔离级别在工做单元的运行期间保持有效。 因为多个用户访问和更改数据,因此必须维护关系数据库数据完整性。并发性指的是多个交互式用户或应用程序能够同时共享资源。隔离级别指定: 应用程序读取和更新的行可供其余并发执行的应用程序进程使用的程度;以及应用程序受其余并发执行的应用程序进程的更新活动的影响程度。优化

DB2 支持下列隔离级别:

  • 可 重复读 (Uncommitted Read - UR):在工做单元内,应用程序读取的全部行都被锁定。所以,当游标处于打开状态时,在同一工做单元内发出两次的查询将返回相同的结果。在该工做单元完成 以前,其余应用程序没法更新、删除或插入可能会影响结果表的行。“可重复读”应用程序看不到其余应用程序的未落实更改。
  • 读 稳定性 (Read Stability - RS):读稳定性只锁定应用程序在工做单元内检索的那些行。它确保在某个工做单元完成以前,在该工做单元运行期间的任何限定行读取不被其余应用程序进程更 改,且确保不会读取由另外一个应用程序进程所更改的任何行,直至该进程落实了这些更改。
  • 游标稳 定性 (Cursor Stability - CS):对于事务访问的行,只要游标定位在该行上,就锁定该行。此锁定在访存下一行或事务终止以前一直有效。可是,若是更改了某一行上的任何数据,则在对 数据库落实更改以前必须挂起该锁定。所以,在同一个工做单元内发出了两次的查询可能不会返回相同的结果。
  • 未落实的读 (Uncommitted Read - UR) :应用程序能够访问其余事务的未落实更改。除非其余应用程序尝试删除或改变该表,不然该应用程序也不会锁定正读取的行而使其余应用程序不能访问该行。此隔离级别一般用于对只读表执行的查询。

要提供并发性控制并防止无控制的数据访问,数据库管理器将锁定置于缓冲池、表、数据分区、表块或表行上。锁定将数据库管理器资源与应用程序关联(称为锁定全部者),以控制其余应用程序访问同一资源的方式。

锁的基本属性包括:object, size, duration, mode。若是您对这些属性所表达的含义不是特别清楚,建议您先读一下相关文章和 DB2 infocenter 里相关章节。咱们在这里只是罗列出本文所须要的必要的信息。

锁的持续时间会根据隔离级别而有所不一样:

  • UR 扫描:除非行数据正在进行更改,不然不挂起行锁定。
  • CS 扫描:仅当游标定位在行上时,才挂起行锁定。
  • RS 扫描:事务期间只挂起符合查询条件的行锁定。
  • RR 扫描:事务期间挂起全部行锁定。

锁状态(mode)

  • IN: 无心向
  • IS: 意向共享
  • NS: 下一键共享
  • S: 共享
  • IX: 意向互斥
  • SIX:在乎向互斥下共享
  • U: 更新
  • NW: 下一键弱互斥
  • X: 互斥
  • W: 弱互斥
  • Z: 超级互斥

锁 兼容:在一个应用程序当前拥有对某个对象的锁定,而另外一个应用程序请求对同一个对象的锁定时,就会出现锁定兼容性问题。当两种锁定方式兼容时,能够赞成对 该对象的第二个锁定请求。若是请求的锁定的锁定方式与已挂起的锁定不兼容,则不能赞成锁定请求。相反,请求要等到第一个应用程序释放其锁定,而且释放全部 其余现有不兼容锁定为止。

 




回页首


表格分析法

也许读完上面的一些概念,仍是会有许多疑问,诸如在不一样的隔离级别下作不一样的操做前须要知足那些条件,以及操做完成后对其余事务到底会产生什么影响?除了从文字上进行推敲,有没有其余的办法帮助咱们分析和判断 DB2 的并行机制? 如今就让咱们进入本文主题。本文将介绍一种表格分析法,借助两种表格,能够对使用不一样隔离级别的事务进行不一样操做时的行为进行精确分析和判断。 这里咱们要引入两类特别重要的表,一张表是用来描述使用不一样隔离级别在作不一样的操所时须要获取的锁类型,另外一张表描述的是锁类型的兼容性。

事务在不一样的隔离级别中进行不一样的操做,获取的锁类型也不一样。好比在最高隔离级别 RR 中修改数据获取的锁 (S) 与 CS 级别中读数据获取的锁(NS) 就不同。 另外访问方案也可对锁定方式具备很大影响。举例来讲,当使用索引扫描来查找特定行时,优化器将可能为该表选择行级别锁定(IS);而若是不使用索引,则必须按顺序扫描整个表来找到所选的行,而且可所以获取单个表级锁定(S)。 所以获取的锁定的类型依赖于隔离级别、操做类型,以及访问方案诸多因素。为了简单起见,咱们只对标准表,使用索引和所有扫描的访问方案来作分析。本文没有涉及集群表中的锁定以及被延迟的数据页访问方式。

表1描述了在不使用谓词 ( SQL 语句里的 where 部分) 进行表扫描的访问方式下,在不一样隔离级别下作各类操做所要获取的锁定方式。


表 1. 不使用谓词的表扫描的锁定方式

隔离级别 只读和模糊扫描 游标-扫描 游标-当前行 更新或删除-扫描 更新或删除-更新行
RR S/-- U/-- SIX/X X/-- X/--
RS IS/NS IX/U IX/X IX/X IX/X
CS IS/NS IX/U IX/X IX/X IX/X
UR IN/-- IX/U IX/X IX/X IX/X

 

表2描述了在使用谓词 ( SQL 语句里的 where 部分) 进行表扫描的访问方式下,在不一样隔离级别下作各类操做所要获取的锁定方式。


表 2. 使用谓词的表扫描的锁定方式

隔离级别 只读和模糊扫描 游标-扫描 游标-当前行 更新或删除-扫描 更新或删除-更新行
RR S/-- U/-- SIX/X U/-- SIX/X
RS IS/NS IX/U IX/X IX/U IX/X
CS IS/NS IX/U IX/X IX/U IX/X
UR IN/-- IX/U IX/X IX/U IX/X

 

表3描述了在使用具备惟一性的索引来进行索引扫描的访问方式下,在不一样隔离级别下作各类操做所要获取的锁定方式。


表 3. 使用单个合格行的 RID 索引扫描的锁定方式

隔离级别 只读和模糊扫描 游标-扫描 游标-当前行 更新或删除-扫描 更新或删除-更新行
RR IS/S IX/U IX/X IX/X IX/X
RS IS/NS IX/U IX/X IX/X IX/X
CS IS/NS IX/U IX/X IX/X IX/X
UR IN/-- IX/U IX/X IX/X IX/X

在上面两张表中,每一项由两部分组成: 表锁定和行锁定,横线表明没有该对象上的锁定。

表4显示锁定之间的兼容关系。Y 表示锁定兼容,即当一个进程挂起或正在请求对同一个资源的锁定时,能够赞成锁定请求。N 表示锁定不兼容,请求者必须等待,直到全部不兼容的锁定被其余进程释放为止。


表 4. 锁定类型兼容性

IN IS NS S IX SIX U X Z NW W
Y Y Y Y Y Y Y Y Y Y Y Y
IN Y Y Y Y Y Y Y Y Y N Y Y
IS Y Y Y Y Y Y Y Y N N N N
NS Y Y Y Y Y N N Y N N Y N
S Y Y Y Y Y N N Y N N N N
IX Y Y Y N N Y N N N N N N
SIX Y Y Y N N N N N N N N N
U Y Y Y Y Y N N N N N N N
X Y Y N N N N N N N N N N
Z Y N N N N N N N N N N N
NW Y Y N Y N N N N N N N Y
W Y Y N N N N N N N N Y N

下面咱们经过一个实例来看一个如何根据这些表格来分析 DB2 中的锁机制。首先须要准备试验环境。

这里咱们使用了 DB2 V9,实际上这些试验一样适用于 DB2 V8。

首先运行 db2cmd,初始化 DB2 CLP (命令行处理器)的运行环境,而后输入 db2,进入命令行处理器。建立数据库,链接到数据库,建立表格,以及插入两条试验数据,具体操做如 listing1:


清单 1. 建立数据库和表


db2 = > CREATE DATABASE TESTDB USING CODESET UTF-8 TERRITORY US COLLATE USING SYSTEM
DB20000I CREATE DATABASE命令成功完成。
db2 = > connect to TESTDB

数据库链接信息

数据库服务器 = DB2/NT 9.1.0
SQL 受权标识 = QKK
本地数据库别名 = TESTDB

db2 = > create table test (id varchar(2) not null, name varchar(20), primary key(id))
DB20000I SQL命令成功完成。
db2 = > insert into test values('1', 'a')
DB20000I SQL命令成功完成。
db2 = > insert into test values('2', 'b')
DB20000I SQL命令成功完成。

 

然 后咱们使用两个 CLP 做为两个独立的进程,分别运行事务,来观察和分析 DB2 中的锁机制。两次运行 db2cmd,打开两个 DB2 的命令运行环境,而后输入 db2 +c,进入命令行处理器。加参数 +c 是为了保证在 DB2 CLP 中的 SQL 语句不自动提交,这种方式下,只有输入命令 commit 或者 rollback 才会结束一个事务。

在第一个 CLP 中运行


清单 2. 事务处理一


db2 +c
db2 => connect reset
DB20000I SQL 命令成功完成。
db2 => change isolation to RS
DB21053W 链接不支持 RS 的数据库时会进行自动升级。
DB20000I CHANGE ISOLATION 命令成功完成。
db2 => connect to testdb

数据库链接信息

数据库服务器 = DB2/NT 9.1.0
SQL 受权标识 = QKK
本地数据库别名 = TESTDB

db2 => select * from test

ID NAME
-- --------------------
1 a
2 b

2 条记录已选择。

db2 => update test set name='abc' where name='a'
DB20000I SQL 命令成功完成。

 

咱们设定了事务的隔离级别为读稳定 (RS),而且修改了一条数据。注意这里没有 commit,由于咱们不想结束这个事务。

在第二个 CLP 中,运行


清单 3. 事务处理二


db2 +c
db2 => connect reset
DB20000I SQL 命令成功完成。
db2 => change isolation to RS
DB21053W 链接不支持 RS 的数据库时会进行自动升级。
DB20000I CHANGE ISOLATION 命令成功完成。
db2 => connect to testdb

数据库链接信息

数据库服务器 = DB2/NT 9.1.0
SQL 受权标识 = QKK
本地数据库别名 = TESTDB

db2 => select * from test where id='2'
ID NAME
-- --------------------
2 b

1 条记录已选择。

db2 =>select * from test where id='1'

 

在第二个事务中,咱们会发现可以读取出 id='2' 的数据,可是当查询 id='1' 的数据时,没有数据输出,说明发生了锁等待。

下面咱们来分析一下:事务一执行 update 操做时,由于在 where 语句中使用的是name='a',没有包括键值,因此数据库管理器在进行操做时会进行全表扫描。 咱们就应该看使用谓词的表扫描的锁定方式表格,从表 2 中能够判断,在 RS 级别下,更新的行(被修改的第一行)加 X 锁,更新-扫描行(第二行)加 U 锁。

事务二在执行select * from test where id='2' 时,where 语句中使用主键值,对于主键是建有索引的,所以根据表 3 (惟一匹配的索引扫描锁定表)来判断须要获取的锁模式)。 从表中能够看出,RS 级别下被扫描的行(只有第二行)须要申请 NS 锁。第二行已经被事务一加了 U 锁,那么事务二可否获取到 NS 锁呢,这就要看 U 锁和 NS 锁是否兼容。 这时须要查看锁的兼容表。从表4中能够看出,U 和 NS 是兼容的,所以事务二能够获取锁,可以完成查询操做。

当事务二执行select * from test where id='1' 时,从表 3 中查出,被扫描的第一行须要 NS 锁,与事务一加的 X 锁不兼容,所以发生锁等待。 这时只有第一个事务运行 commit 或者 rollback,事务一结束,X 锁被释放,第二个事务才能够获取到 NS 锁,获得查询结果。

那么若是在事务二中运行select * from test where name='b'呢?这条语句是要查询的第二条记录,须要的 NS 锁与事务一在这条记录上的U锁是兼容的,应该不会发生锁等待,能够获得结果。事实是否是这样呢? 作一下试验就会发现,也是须要等待的,为何呢?仔细想一想就会发现,语句select * from test where name='b' 并无使用索引,而是作全表扫描,从 table 1 中能够获得,在隔离级别为 RS 的事务中,全部被扫描的行都须要 NS 锁,包括第一行,与第一行记录已经有的 X 锁是不兼容的,所以当扫描到第一行时就须要锁等待了。

 




回页首


试验

热身完毕,咱们已经基本了解如何使用获取锁表和锁兼容表来分析事务的行为了。下面咱们针对各个隔离级别分别设计一些试验来理解隔离级别的意义。

未提交的读隔离级别 (UR) 是最不严格的隔离级别。实际上,在使用这个隔离级别时,仅当另外一个事务试图删除或更改被检索的行所在的表时,才会锁定一个事务检索的行。 由于在使用这种隔离级别时,行一般保持未锁定状态,因此脏读、不可重复的读和幻像均可能会发生。下面咱们就设计几个试验看如何发生的脏读,不可重复的读以及幻像。

试验一:UR下的脏读

当事务读取还没有提交的数据时,就会发生脏读。例如:事务 1 更改了一行数据,而事务 2 在事务 1 提交更改以前读取了已更改的行。若是事务 1 回滚该更改,则事务 2 就会读取被认为是未曾存在的数据。咱们用试验来感觉一下。

首先在事务一中执行connect reset; change isolation to UR; connect to testdb 来把事务的隔离级别修改成UR。 第二个事务能够采用任何隔离级别,只须要保证在第二个窗口中采用不自动提交方式进入 CLP。在第二个事务中运行 update test set name='abc' where id='01' , 而后在第一个事务中运行 select * from test 。这里读取的数据是第二个事务已经修改过的,可是尚未提交的数据。在第二个事务运行 rollback,取消刚才的修改, 在第一个事务运行 select * from test ,发现读取的数据又变成了修改前的数据。


图 1. UR 下的脏读(事务一)
UR 下的脏读(事务一)

图 2. UR 下的脏读(事务二)
UR 下的脏读(事务二)

事务一的第一次读,读到的就是脏数据。对照表格稍微分析一下就能够得知,发生脏读缘由就是 UR 事务读操做的时候不对行进行的锁定,这样一方面事务自己读数据时不受约束,同时因为不对数据进行锁定,那么使得其余事务修改时也就不受 UR 事务的约束了。

游标稳定性隔离级别在隔离事务效果方面很是宽松。它能够防止脏读;但有可能出现不可重复的读和幻像。

试验二:CS 下如何防止脏读

把事务一的隔离级别改成 CS,而后重复上面的试验,会发如今事务二修改数据之后,在事务二提交之前,事务一去读修改的数据时会发生锁等待,直到事务二 commit 或者 rollback,所以不会发生脏读。试验过程以下图所示:


图 3. CS 下防止脏读(事务一)
CS 下防止脏读(事务一)

图 4. CS 下防止脏读(事务二)
CS 下防止脏读(事务二)

仍是利用咱们的表格分析法,从表中能够看出,不管采用什么访问方式,在 CS 隔离级别下,读取数据时须要在行上加 NS 锁,若是此时数据上己经有其余事务加了X锁(被修改时加X锁),因为 NS 锁与 X 锁的不兼容,使得读数据的时候必定发生锁等待。

试验三:CS 下不可重复的读

CS 能够防止脏读,可是可能出现不可重复的读和幻像。以下图试验所示:事务一读完数据后,事务二修改数据(或者添加数据),而且提交,那么事务一再去读数据, 会发生同一条记录跟上一次读的结果不同,这就是不可重复的读(也会发生读出上一次没有查询到的记录,这就是幻像)。


图 5. CS 下不可重复的读(事务一)
CS 下不可重复的读(事务一)

图 6. CS 下不可重复的读(事务二)
CS 下不可重复的读(事务二)

但 是若是咱们仔细按照表格分析就会产生这样的疑问:当第一个事务读取数据之后,按照表格1,数据应该加 NS 锁,事务二在修改数据的时候,须要在被修改数据行上加X锁,表 4 中看出,X 锁与 NS 锁不兼容,事务二应该是锁等待阿,然而状况并不是如此。从试验中能够看出,事务二在修改数据时没有等待。 不过这确实符合 CS 隔离级别的描述,CS 级别下,只读取已经提交了数据,可是读取并不会影响其余事务对数据的修改。可是与表格分析得出的结果矛盾,缘由在哪儿呢? 咱们能够借助 DB2 活动监视器来分析一下。咱们先来复现一下事务一读取数据之后的状态。首先在经过 commit 来结束事务一,而后从新执行 select 语句以恢复到读取后的状态。 打开 DB2 活动监控器,选择数据库 testdb,而后在检视任务中选择正在解决应用程序锁定状态,点击完"成看"。 在报告栏中选择"具备最大锁定超时数的应用程序",找到第一个 CLP 对应的程序,右击,选择显示锁定链,以下图:


图 7. 显示锁定链
显示锁定链

在应用程序锁定链窗口中,右击程序,选择显示锁定详细信息。


图 8. 显示锁定详细信息
显示锁定详细信息

在详细信息窗口中,会列出此程序已经拥有的锁定和等待的锁定。在这里面,咱们并无发现针对行对象的 NS 锁。 咱们分别在 CS 级别和 RS 级别下作相同的查询操做,而后比较它们的锁定情况,就会更清楚地发现它们的不一样,尽管在表 1 和表 2 里 CS 和 RS 看起来是同样的。


图 9. CS 级别锁定情况
CS 级别锁定情况

图 10. RS 级别锁定情况
RS 级别锁定情况

什么缘由呢?是否是表 1 和表 2 把锁模式弄错了,CS 隔离级别下读操做也不须要 NS 锁?应该也不是,由于若是咱们让事务二先修改数据,再让事务一去读,就回发生锁等待,所以事务一读数据时,仍是须要行的 NS 锁,不然象 UR 级别同样,也会发生脏读。 回头看一下咱们在基本概念一节中的这样一段话:

锁的持续时间会根据隔离级别而有所不一样:

  • CS 扫描:仅当游标定位在行上时,才挂起行锁定。

也就是说在只有游标操做方式的时候,才会在当前行加锁,象咱们所作的只读操做,不会持续占用锁,读操做完成后锁就会被释放掉,不会等到事务结束,这样就能够解释在 CS 级别下,为何对数据的读取不会阻塞其余事务对该数据的修改了。

读稳定性隔离级别:读稳定性隔离级别能够防止脏读和不可重复的读,可是可能出现幻像。在使用这个隔离级别时,只锁定事务实际检索和修改的行。

试验四:RS 下防止不可重复的读

将事务一的隔离级别改成 RS,而后执行select * from test where id='1' 读取数据; 事务二执行update test set name='abc' where id='1'来修改数据,会发现事务二发生锁等待。 事实上,不管第二个进程使用何种隔离级别,现象是同样的。仍是用锁兼容表分析一下。在事务一读数据时,由于是索引扫描,所以从表 3 中得出,只是在被检索的数据上加 NS 锁(不一样于 CS,锁会持续到事务结束), 事务二在作修改的时候,获取 X 锁,与 NS 不兼容,所以锁等待,直至事务一结束。这样就保证了在事务一的操做过程当中,被检索的行不会被其余进程修改,每次读到的同一条记录都不会有变化,从而保证了读稳定。


图 11. RS 下防止不可重复的读(事务一)
RS 下防止不可重复的读(事务一)

图 12. RS 下防止不可重复的读(事务二)
RS 下防止不可重复的读(事务二)

试验五:RS 下的幻像

那么如何发生的幻像呢?事务一读取数据后,在事务二中插入一条数据insert into test values('3', 'c') 而且提交, 再在事务一中执行select * from test, 就会发现与前一次 select 结果相比,多了一条数据,这就是幻像。


图 13. RS 下的幻像(事务一)
RS 下的幻像(事务一)

图 14. RS 下的幻像(事务二)
 RS 下的幻像(事务二)

可重复读隔离级别:可重复读隔离级别是最严格的隔离级别。在使用它时,一个事务的影响彻底与其余并发事务隔离:脏读、不可重复的读、幻像都不会发生。咱们如今就来验证一下。

试验六:RR 下防止幻像

事务一隔离级别改成 RR,而后执行select * from test , 事务二执行insert into test values('4', 'd'),会发现事务二发生锁等待。 仍是用表格分析法,事务一读取数据时,从表1能够看出,在表上加了 S 锁,事务二插入数据时须要在表上申请 X 锁,X 与 S 不兼容,所以进程二只能锁等待。


图 15. RR 下防止幻像(事务一)
RR 下防止幻像(事务一)

图 16. RR 下防止幻像(事务二)
RR 下防止幻像(事务二)

这样咱们就会很天然地发现,隔离级别越高,操做越安全,可是发生锁等待的机会就越大,效率会下降,甚至会发生死锁。下面咱们再来设计一个死锁的例子。

两个进程中的事务都采用 RS (读稳定)的隔离级别,事务一执行select * from test where id='1', 事务二执行select * from test where id='2', 事务一执行update test set name='bb' where id='2', 事务二执行update test set name='bb' where id='1'。 这种状况下,就会发生事务一在执行 update 的时候等待事务二的 NS 锁被释放,而事务二执行 update 的时候也会等待事务一的 NS 锁别释放,这样就发生了死锁。 好在 DB2 能进行死锁检测,当发现死锁时,会中断并回滚其中一个事务,另外一个事务就能够继续了。


图 17. RR 下防止幻像(事务三)


图 18. RR 下防止幻像(事务四)




回页首


总结

咱们已经经过试验的方法,掌握了如何运用表格来分析和判断事务在不一样的隔离级别下,执行不一样的操做时的行为,从而可以更加深入理解 DB2 的并发机制。 您能够用这种方法来对您的数据库并发操做方案进行分析和评估,来判断是否知足实际需求。可是要指定好的并发操做方案,仍是须要对数据库的并发机制有着融会贯通的理解。


参考资料


做者简介

齐克科 2001年加入IBM,目前就任于中国开发中心,从事MBPS(Managed Business Process Services)相关的工做。

 

Elaine Zhan 中国开发中心 MBPS develope lead,负责CSDP common service 和 provisioning 的开发工做。

 

陈荔龙 2004年加入IBM,目前就任于中国开发中心,从事MBPS(Managed Business Process Services)相关的工做。

相关文章
相关标签/搜索