锁的工作原理
我们知道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 数据库管理程序总是尝试获取行级锁。但是,可以通过执行特殊形式的 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 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)表级锁。
参考至:http://www.ibm.com/developerworks/cn/education/data/db2-cert7306/section5.html
本文原创,转载请注明出处、作者
如有错误,欢迎指正
邮箱:czmcj@163.com