ITPub博客

首页 > Linux操作系统 > Linux操作系统 > 通过改善锁提高应用程序并发性

通过改善锁提高应用程序并发性

原创 Linux操作系统 作者:ArtCode 时间:2009-04-02 14:41:02 0 删除 编辑

前提

为什么要引入这三个变量呢?在 DB2 没有这三个变量前,如果一个用户正在更改(update)、插入(insert)或删除(delete)一行数据,那么 DB2 会在这一行加上排他锁(Exclusive),别的用户不能读写,除非使用 UR 隔离级别。

其实目前市场上除了 Oracle 所有的数据库包括 DB2、Informix、SQL Server 和 Sybase 对锁的控制都是这种方式。而 Oracle 由于有回滚段(rollback segment),所以在 Oracle 数据库中对于 insert 一行,回滚段记录该插入记录的 rowid ;对于 update 操作,回滚段记录更新字段的旧值(before image);对于 delete 操作,回滚段记录整行的数据。由于 Oracle 有了回滚段,所以可以实现多版本读。所以在用 Oracle 数据库开发时,很少关注锁的情况,因为大部分情况下你都是可以读的,只不过有的时候大不了读以前的“ before image ”罢了。

因此很多使用 Oracle 开发的用户在转向 DB2 开发时,都特别郁闷。而 DB2 为了改善应用程序并发性,从 DB2 V8 以后就陆续引入了这三个变量。这三个变量也是 DB2 的客户提出要 IBM 去改进的,这种需求最初是 SAP 提出的。这三个变量并不会改变锁的本质,只不过是了解它们的工作方式和机制可以使我们根据我们的业务逻辑来合理的设置调整以提高应用程序并发。下面我们先通过一个例子来说明没有这三个变量之前的一些锁的情况。

假设 t1 表中有 5 条记录,分别为 11,22,33,44,55 。其中第 2 条记录 22 被删除了,现在有一个 session1 要重新插入一条新的记录 22 ;同时第二个 session 2 执行了 db2 select * from t1 where id >11 and id<44,正常的它应该检索到 33 这条记录,但是由于现在插入的记录 22 也包含在这个谓词限定范围内,这个时候 session 2 处于 lockwait 状态。

Session 1 Session 2
db2 CONNECT TO SAMPLEdb2 +c INSERT INTO t1 VALUES(22) 

db2 CONNECT TO SAMPLEdb2 SELECT * FROM t1 WHERE id >11 and id <44 

我们通过监控看到如下图所示。


图 1. 监控两个运行的 Session
 监控两个运行的 Session

从 DB2 的角度来说好像这是合理的,但是从用户角度和业务逻辑来说希望这个时候能够读取到数据,那么怎么解决这个矛盾呢?下面我们来仔细讲解这三个变量。

设置 DB2_EVALUNCOMMITTED

DB2 V8.1.4 版本中首次引入了 DB2_EVALUNCOMMITTED 这个 DB2 注册表变量。当它被启用(=TRUE | ON | YES | 1)时,它将修改 DB2 中只读查询的行为,使之允许在索引扫描(必须是 Type 2 索引,对于 Type 1 索引该特性不受支持)或表访问时推迟锁,直到限定语句的所有谓词都是已知的。引入这个新的注册表变量是为了可选地提高一些应用程序的并发性,其实质是允许读扫描推迟或避免行锁,直到适合特定查询的一个数据记录成为已知。

注: 在 DB2 V8.1 和更高版本中,所有新索引都创建为 Type 2 索引。一个例外是当您在已具有 Type 1 索引的表上添加索引时。仅在这种情况下,新索引也将是 Type 1 索引。要了解一个表存在什么类型的索引,执行 INSPECT CHECK 命令。要将 Type 1 索引转换为 Type 2 索引,执行 REORG INDEXES CONVERT 命令。

在 DB2 V8.1.4 之前(并且没有设置这个注册表变量),DB2 将执行保守式的锁:在验证行是否满足查询的排除谓词之前,它将锁定每个被访问的行。不管数据行是否被提交,以及根据语句的谓词它是否被排除,对于索引扫描和表访问都执行这样的锁定操作。下面我们举一个简单的例子:


清单 1. DB2 8 之前的执行保守式的锁

			 
db2 create table t1(id int) 
 db2 insert into t1 values(11) 
 db2 commit 
 
 

现在有两个 session 分别发出了下面的 SQL 语句。

Session 1 Session 2
db2 CONNECT TO SAMPLEdb2 +c INSERT INTO t1 VALUES(22) 

db2 CONNECT TO SAMPLEdb2 SELECT * FROM t1 

我们查看 session 2 的状态。


图 2. 查看 session 2 的状态
查看 session 2 的状态

第一个语句 DB2 +C INSERT INTO TABLE T1 VALUES (22) 阻塞所有其他的扫描器,因为它持有行上的锁,如果第二个 session 执行 DB2 SELECT * FROM T1 将被阻塞,直到事务 1 提交或回滚。但是我们假设第二个语句是 DB2 SELECT * FROM T1 WHERE id=11 。

在此情况下,即使事务 2 与列 ID=22 中的任何值(还没有被提交)都没有关系,它也仍将被阻塞,处于锁等待(lockwait)状态。在 DB2 中,默认情况下将发生这一系列的事件,因为默认的隔离级别是 Cursor Stability (CS),这种隔离级别表明,一个查询访问的任何一行在游标定位到该行时都必须被锁定。在语句 1 释放它用于更新表 T1 的锁之前,语句 2 不能包含表 T1 第一行上的锁。如果 DB2 知道值 ID=11 不是语句 2 的数据请求的一部分(换句话说,它在锁行之前计算了谓词),就可以避免阻塞,这是合情合理的,因为语句 2 将不会尝试锁定表中的第一行。

现在我们启用 DB2_EVALUNCOMMITTED 注册变量,该实例设置后需要重启实例。


清单 2. 重起 DB2 实例

			 
db2set DB2_EVALUNCOMMITTED=ON – i 
 db2stop force 
 db2start 
 

在启用该实例后,我们发现再重复刚才的实验,我们发现第二条 SQL 语句“ select * from t1where id=11 ”可以执行而不会被阻塞。所以 DB2_EVALUNCOMMITTED 注册变量作用是判断该 SQL 谓词所扫描的行是否有锁,如果没有那么可以检索到数据。

当 evaluate uncommitted 第一次在 DB2 V8.1.4 中引入时,它带有以下限制:

  • 该特性只能用于 CS 和 RS 隔离级别。
  • Sargable 谓词必须存在,以便计算。
  • 锁避免不适用于编目表上的扫描。
  • 当扫描一个 MDC 表时,对于索引扫描,块锁可以推迟。然而,对于表扫描,块锁不会推迟。
  • 被推迟的锁不会发生在正在执行在线表重组的表上。
  • Index Manager 不可能在没有锁行的情况下回调 Data Manager 来取数据记录。这意味着 ISCAN-Fetch 计划不能在 Data Manager 中推迟锁(惟一的例外是对一个 MDC 表的块索引,它的 Index Evaluation 谓词是一个 ISCAN 计划。)

DB2 V8.2.2 通过去掉 DB2 V8.1.4 中第一阶段的 Evaluate Uncommitted,改进了这些缺点。 DB2 V8.2.2 引入了名为 DEFERISCANFETCH 的注册表变量,作为 DB2_EVALUNCOMMITTED 的新设置。启用该变量时,由该特性承担的锁避免将使用 ISCAN-FETCH 数据计划。

DB2_EVALUNCOMMITTED 变量影响 DB2 在游标稳定性(CS)和读稳定性(RS)隔离级别下的行锁机制。当你启用该功能时,DB2 可以对于未提交的插入(INSERT)或者更新(UPDATE)数据进行谓词判断,如果未提交数据不符合该条语句的谓词判断条件,DB2 将不对未提交数据加锁,这样避免了因为要对未提交数据加锁而引起的锁等待状态,提高了应用程序访问的并发性。同时 DB2 会无条件进行表扫描时忽略删除的行数据(不管是否提交)。

这里分两部分来看待,“对于 插入(INSERT)或者更新(UPDATE) 如果未提交数据不符合该条语句的谓词判断条件,DB2 将不对未提交数据加锁。”这样虽然比不上 ORACLE 对于符合该条语句的谓词判断条件可以从回滚段里面读出 “ before image ”那样做到写不阻止读,但是起码一定程度了缓解了锁的问题,不会因为插入(INSERT)或者更新(UPDATE)一条记录造成整个表都锁住。这点是个进步,个人觉得也不会造成什么大的负面影响,

下面我们通过一个实验来说明这点:


清单 3. 改进锁的实验

			 
db2 create table t1(id int) 
 db2 insert into t1 values(11) 
 db2 insert into t1 values(22) 
 db2 commit 
 
 现在表中有两条记录 11 和 22。
 

现在两个 session 发出了下面的 SQL 语句

Session 1 Session 2
db2 CONNECT TO SAMPLEdb2 +c delete from t1 where id=22 

db2 CONNECT TO SAMPLEdb2 SELECT * FROM t1 WHERE id=11 

在未设置 DB2_EVALUNCOMMITTED=ON 时,session2 肯定是处于锁等待(lockwait 状态的),现在我们设置了 DB2_EVALUNCOMMITTED=ON 后,我们来看看 session 2 能否检索到数据。

Session 1 Session 2
db2 CONNECT TO SAMPLEdb2 +c delete from t1 where id=22 






db2 CONNECT TO SAMPLE
C:\>db2 select * from t1 
  ID 
  ----------- 						   
  11 						   
 1 条记录已选择。

通过上面的实验我们发现在启用 DB2_EVALUNCOMMITTED=ON 时,对于 delete 操作的处理 ,DB2 会无条件进行表扫描时忽略删除的行数据(不管是否提交)。个人觉得有很大的问题,通过上面的这个测试,一个事务删除一条记录并没提交,另外一个会话查询的时候已经没有这条记录了,这相当于 UR 隔离级别。这样显然是不符合业务要求的。与其这样还不如锁住。所以用 DB2_EVALUNCOMMITTED=ON 时,对删除的时候应该注意多多测试。

现在我们在 t1 创建一个 type-2 的索引,我们再来做刚才的那个实验。


清单 4. 包含索引的实验

			 
db2 create index index11 on t1(id) 
 db2 reorg indexes all for table oracle.t1 convert 转变为 type-2 索引
 

Session 1 Session 2
db2 CONNECT TO SAMPLE 
 db2 create index index11 on t1(id) 
 db2 reorg indexes all for table oracle.t1 convert 
 db2 +c delete from t1 where id=22
 

db2 CONNECT TO SAMPLE
C:\>db2 select * from t1 
--lockwait 挂起



我们在另外一个窗口查看 session 2 的状态,发现 session 2 处于 lockwait 状态。


图 3. 查看 session 2 的状态
查看 session 2 的状态

当您的 DB2 环境中启用了 evaluate uncommitted 行为时,您应该清楚,谓词计算可能发生在未提交的数据上。而且,在表扫描访问中,被删除行被无条件忽略,而对于 type-2 索引扫描,被删除的键不会被忽略(除非您还设置 DB2_SKIPDELETED 注册表变量,DB2_SKIPDELETED 变量我们稍后介绍)。如果您要在环境中单独设置 DB2_SKIPDELETED 注册表变量,DB2 将允许在表扫描访问时无条件地忽略被删除行,并忽略 type-2 索引扫描访问的伪删除索引键。

设置 DB2_SKIPDELETED

DB2_SKIPDELETED=ON 该变量被启用时,将允许使用 Cursor Stability 或 Read Stability 隔离级别的语句在索引扫描期间无条件地跳过被删除的键,而在表访问期间则无条件地跳过被删除的行。当 DB2_EVALUNCOMMITTED 被启用时,被删除的行会被自动跳过,但是除非同时启用了 DB2_SKIPDELETED,否则 type-2 索引中未提交的伪删除键不会被跳过。

在上面的实验中,我们发现当我们设置了 DB2_EVALUNCOMMITTED 变量时,如果表上有 type-2 索引,那么在我们读取数据时,被删除的索引键不会被忽略。这种情况下如果你希望跳过被删除的键,可以通过设置 DB2_SKIPDELETED=ON 来实现,下面我们做个实验。


清单 5. 设置 DB2_SKIPDELETED=ON

			 
db2set DB2_SKIPDELETED=ON – i 
 db2stop force 
 db2start 
 
 

设置生效后,我们接着做刚才的实验。

Session 1 Session 2
db2 CONNECT TO SAMPLE 
 db2 create index index11 on t1(id) 
 db2 reorg indexes all for table oracle.t1 convert 
 db2 +c delete from t1 where id=22 

 

db2 CONNECT TO SAMPLE
C:\>db2 select * from t1 
   ID 
  ----------- 					   
   11   					   
 1 条记录已选择。

我们可以看到在设置 DB2_SKIPDELETED=ON 后,即使 t1 表上有 type-2 的索引,那么在扫描的时候仍然忽略这个删除的行。但是这个用的时候一定要结合业务逻辑使用,因为这种情况下等同于“脏读“,所以一定多测试。

设置 DB2_SKIPINSERTED

虽然当一个行由于一个未提交的 INSERT 而被锁的时候,这种行为是正确的 —— 但是有些情况下应用程序的所有者希望 DB2 忽略正在等待提交的被插入的行,就好像它不在一样(由于未提交 INSERT 的提交版本现在根本没有行,所以这是可能的)。例如,银行下午 5 点左右想统计今天的业务量,这时只是想了解大概的业务量而不是精确的,这种情况下如果启用该变量,那么,遗漏一两笔业务是可以接受的。

在 DB2 V8.2.2 中,DB2_SKIPINSERTED=OFF 是默认设置。这使得 DB2 的行为和预期的一样:扫描器一直等到 INSERT 事务提交或回滚,然后返回数据 —— 这和平常一样。取决于您的应用程序以及和业务逻辑相关的数据完整性的特征,这样可能合适,也可能不合适。

例如,考虑一个涉及两个应用程序的业务流程,这两个应用程序使用相同的一个表来交换业务信息 —— 例如,一个信用评级应用程序和信用评分引擎。应用程序 A 基于一个 Web 表单将数据插入数据库,应用程序 B 读这些数据。为了加快信用审批的速度,由于候选者通过信用评级应用程序表单转移,信息块通过表单中的 'Steps' 被发送到应用程序 B(通过公共的表)。当候选者完成信用评级应用程序流程中的每个步骤时,信息被发送。在这个环境中,数据必须由第二个应用程序按照表中给出的顺序来处理,以便当接下来要读的行要被应用程序 A 插入时,应用程序 B 必须等待,直到 INSERT 被提交。

如果设置 DB2_SKIPINSERTED=ON,DB2 将把未提交的 INSERT(只对于 CS 和 RS 隔离级别)看作它们还没有被插入。该特性增加了并发性,同时又不牺牲隔离语义。 DB2 为扫描器实现了这种能力,通过锁属性和锁请求的反馈,使其忽略未提交的插入行,而不是等待。

下面我们来看设置 DB2_SKIPINSERTED 变量前后的例子。

Session 1 Session 2
db2 CONNECT TO SAMPLE 
 db2 create index index11 on t1(id) 
 db2 reorg indexes all for table oracle.t1 convert 
 db2 +c insert into t1 values(33)
 

db2 CONNECT TO SAMPLE
C:\>db2 select * from t1 
 -- 挂起,处于 lockwait 状态



通过监控发现 session 2 处于 lockwait 状态。


图 4. 监控查看 session 2 的状态
监控查看 session 2 的状态

如果这种情况下 session2 希望能够跳过未提交 insert 的数据而得到数据,那么可以设置 DB2_SKIPINSERTED 注册变量。


清单 6. 重起 DB2 实例

			  
db2set DB2_SKIPINSERTED=ON – i
db2stop force
db2start


然后再重复刚才的实验,我们发现这个时候,session2 可以读到数据。

Session 1 Session 2
db2 CONNECT TO SAMPLE 
 db2 create index index11 on t1(id) 
 db2 reorg indexes all for table oracle.t1 convert 
 db2 +c insert into t1 values(33) 

 

C:\>db2 select * from t1 
 ID 
 -----------    
 11 
 22 
 2 条记录已选择。 

总结

总的来说这 3 个注册变量会影响到并发性。通过合理设置这些变量可以改善并发性,但是也会影响到应用程序的行为。建议在 DB2 开发设计的初期启用这些注册变量,从而在实现并发性增强后执行全面测试中的所有单元测试。

通过这三个注册变量的设置,可以提高并发,但是我们使用的时候一定要结合自己的业务逻辑使用。根据个人的经验,我建议使用 DB2_EVALUNCOMMITTED=ON 和 DB2_SKIPINSERTED=ON,对于 DB2_SKIPDELETED 变量使用的时候一定要充分的测试,因为它等同于使用 UR 隔离级别(注,虽然 DB2_SKIPINSERTED=ON 也等同于 UR,但是没有插入的数据反正也没有插入,读不到在业务上是可以接受的)。

总之,有这三个变量,我们多了一份选择总比没有强。目前这三个变量都是实例级别的变量,如果能做成 SQL 级或应用级的就更好了。期待 DB2 能够在后续的版本中继续对并发作出改进。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/15082138/viewspace-584369/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2008-08-05

  • 博文量
    269
  • 访问量
    558152