对于获得最佳的应用程序性能,以及确保数据完整性和一致的应用程序行为来说,数据库锁定策略是非常重要的。在 Paul Ilechko 的文章 “Locking strategies for database access”(developerWorks,2006 年 3 月)中,他描述了进行数据库锁定的逻辑会话锁定方式。除了事务锁定之外,这种方式可以在更高的级别上控制应用程序的并发性。
很难实现消极会话锁定方式的 bullet-proof 实现。但是,利用 DB2 Version 9.1 for z/OS 中引入的新特性 SKIP LOCKED DATA(这个特性最初是为另一个完全不同的用途设计的,即避免锁定),可以实现一种简单可靠的解决方案。
在许多场景中,应用程序级别上的并发性控制是必要的,例如:
对于消极会话锁定,按照惯例,应用程序需要两个函数 lock(resource) 和 unlock()。
下面是对解决方案的一些需求,这些需求使解决方案在应用程序级别难以实现:
基于 DB2 事务锁的任何会话锁定实现必须解决锁冲突问题。事务锁冲突可以导致事务回滚(sqlcode -911),或者导致不确定地等待锁。如果没有 SKIP LOCKED DATA 这样的数据库概念,这个问题是很难解决的。
作为一个解决方案,建议定义一个 DB2 表,其中包含资源标识符的列表,并在所有应用程序对资源的访问中使用锁定和解锁协议。lock() 函数的实现在一个 SQL fetch 语句中使用 DB2 新功能 skipping locked data。这是该解决方案的关键。
创建一个 DB2 表(在下文中称为 锁表(lock table)),它定义与会话锁定相关的资源。假设逻辑会话锁的范围是某种资源标识符。在上面的全文索引示例中,这个标识符是索引名称。对于每个资源,在这个表中插入一行。
CREATE TABLE LockTable(ResourceId CHAR(10));
INSERT INTO LockTable VALUES('INDEX 1');
INSERT INTO LockTable VALUES('INDEX 2');
|
上面的示例演示如何在锁表中填充两个全文索引资源。每个索引不能由一个以上的应用程序使用。这意味着,应用程序 1 可能使用索引 1,同时应用程序 2 使用索引 2,但是不允许两个应用程序中同时使用索引 1 。
引入一个由两个函数 lock(ResourceId) 和 unlock() 组成的协议,所有应用程序都需要遵守这个协议。具体的接口依赖于实现语言,在这里无关紧要。关键的一点是,所有应用程序在访问会话锁控制的资源 之前,必须调用 lock(resourceId)。当不再需要这个资源时,它们应该调用 unlock()。
lock() 函数的实现必须确保调用者能够立即得到请求的结果(授予锁还是不授予锁)。另外,授予的锁必须不受应用程序中 DB2 事务的影响。因此,lock() 的实现必须在一个单独的线程中打开额外的 DB2 连接。所以,lock() 函数的实现由以下步骤组成:
SKIP LOCKED DATA 特性只获得未被 DB2 锁定的行(例如,如果另一个应用程序持有这个资源上的会话锁,就不能获得这一行)。DB2 并不在获取操作中等待。见 清单 2 中的示例代码。现在,请求的结果必须在主线程中可用。子线程等待主线程的终止信号。如果授予了会话锁,它就持有锁表中一行上的 DB2 更新锁,直到发生以下情况为止:
DECLARE C1 CURSOR FOR
SELECT ResourceId FROM LockTable WHERE ResourceId=:resourceId
FOR UPDATE WITH CS SKIP LOCKED DATA;
OPEN C1;
FETCH C1;
if (sqlca.sqlcode==NO_DATA_FOUND) {
result=indexAlreadyLocked;
} else {
result=lockGranted;
}
|
提供一个 unlock() 函数,它将终止仍然持有锁表中某一行的 DB2 更新锁的子线程。子线程中的终止代码关闭 SQL 游标,并使事务回滚,见 清单 3。因此,会释放这一行的 DB2 更新锁,清单 2 中的下一个 SQL select 语句会看到这一行。
CLOSE C1;
ROLLBACK WORK;
|
对以上方式做一项简单的修改,就可以控制同时访问一个资源集的应用程序数量:
如果锁表中有重复的行(参见 为资源锁定创建一个 DB2 表 一节),就可以对资源进行并发使用。锁表中一个资源的行数决定了可以同时访问这个资源的最大应用程序数量。
INSERT INTO LockTable VALUES('INDEX 1');
INSERT INTO LockTable VALUES('INDEX 1');
INSERT INTO LockTable VALUES('INDEX 2');
INSERT INTO LockTable VALUES('INDEX 2');
INSERT INTO LockTable VALUES('INDEX 2');
|
按照这段代码,最多有两个应用程序可以同时访问 ‘INDEX 1’,最多有三个应用程序可以同时访问 ‘INDEX 2’。
有一种简单可靠的解决方案模式可用于在应用程序级别实现会话锁。它依赖于 DB2 Version 9.1 for z/OS 中的新特性 SKIP LOCKED DATA,且已成功应用于一个 DB2 开发项目。
| 第1页: 第1页 |