进程可以使用lock
命令应用(锁定)和释放(解锁)锁定。锁控制对数据资源(如全局变量)的访问。这种访问控制是约定的;锁及其对应的变量可以具有相同的名称,但是彼此独立。更改锁不会影响同名变量;更改变量不会影响同名锁。
锁本身不会阻止另一个进程修改相关数据,因为Caché不强制执行单边锁。锁只能按照约定工作:它要求相互竞争的进程都在相同变量上实现锁。
锁可以是本地锁(仅可由当前进程访问)或全局锁(可由所有进程访问)。锁命名约定与局部变量和全局变量命名约定相同。
锁保持有效,直到锁它的进程解锁、系统管理员解锁或进程终止时自动解锁。
本章介绍以下主题:
^LOCKTAB
实用程序,它返回与锁表相同的信息。Caché维护一个系统范围的锁表,该表记录所有有效的锁和锁定它们的进程,以及所有等待的锁请求。系统管理员可以使用管理门户界面或^LOCKTAB
实用程序显示锁表中的现有锁或删除选定的锁。还可以使用%SYS.LockQuery
类读取锁表信息。在%SYS命名空间中,可以使用SYS.Lock
类来管理锁表。
可以使用管理门户在系统范围内查看当前持有或请求(等待)的所有锁。从管理门户中,选择系统操作,选择定,然后选择查看锁。查看锁窗口按目录(Directory)的字母顺序显示锁(和锁请求)列表,并按锁名称(引用)按排序顺序显示每个目录内的锁(和锁请求)列表。每个锁由其进程id(所有者)标识,并且有一个ModeCount(锁模式和锁增量计数)。需要使用刷新图标来查看锁和锁请求的最新列表。
ModeCount可以指示特定所有者进程在特定引用上持有的锁。以下是挂起的锁的ModeCount值的示例:
代码 | 描述 |
---|---|
Exclusive | 独占锁,非升级(LOCK+^a(1)) |
Shared | 共享锁,非升级(LOCK+^a(1)#“S”) |
Exclusive_e | 独占锁,正在升级(LOCK+^a(1)#“E”) |
Shared_e | 共享锁,正在升级(LOCK+^a(1)#“SE”) |
Exclusive->Delock | 处于解锁状态的独占锁。锁已解锁,但锁的释放将推迟到当前事务结束。这可能是由标准解锁(lock-^a(1)) 或延迟解锁-^a(1)#“D” 引起的。 |
Exclusive,Shared | 共享锁和排他锁(以任何顺序应用)。还可以指定升级锁;例如,Exclusive_e,Shared_e |
Exclusive/n | 增量排他锁(lock+^a(1) 已发布n次)。如果锁定计数为1,则不会显示计数(但请参见下文)。还可以指定递增共享锁;例如Shared/2。 |
Exclusive/n->Delock | 处于解锁状态的递增排他锁。锁的所有增量都已解锁,但锁的释放将推迟到当前事务结束。在事务内,单个增量的解锁会立即释放这些增量;只有在锁计数为1时发出解锁命令后,锁才会进入解锁状态。此ModeCount值是处于解锁状态的递增锁,当通过无参数锁定命令或不带锁定操作指示符的锁定(LOCK^xyz(1) )通过单个操作解锁之前的所有锁定时,就会发生此ModeCount值。 |
Exclusive/1+1e | 两个专属锁,一个不升级,一个升级。增量计数分别保存在这两种类型的独占锁上。还可以指定共享锁;例如,Shared/1+1E。 |
Exclusive/n,Shared/m | 共享锁和排他锁,两者都具有整数增量。 |
当然,持有锁ModeCount可以表示共享锁或独占锁、升级锁或非升级锁的任意组合-具有或不具有增量。排他锁或共享锁(升级或非升级)可以处于Delock状态。
ModeCount可以指示等待锁定的进程,例如WaitExclusiveExact。以下是等待锁定请求的ModeCount值:
代码 | 描述 |
---|---|
WaitSharedExact | 正在等待完全相同的锁上的共享锁,无论是持有的锁还是之前请求的锁:LOCK +^ a(1,2)#"S" 正在等待锁 ^ a(1,2) |
WaitExclusiveExact | 正在等待完全相同的锁上的独占锁,无论是持有的锁还是之前请求的锁:LOCK+^ a(1,2) 正在等待锁^ a(1,2) |
WaitSharedParent | 正在等待持有的锁或以前请求的锁的父级上的共享锁LOCK+^ a(1)#“S” 正在等待锁^ a(1,2) |
WaitExclusiveParent | 正在等待持有的锁或以前请求的锁的父级上的独占锁:Lock+^ a(1) 正在等待锁^ a(1,2) |
WaitSharedChild | 正在等待持有的锁或先前请求的锁的子级上的共享锁:LOCK+^ a(1,2)#“S” 正在等待锁^ a(1) |
WaitExclusiveChild | 正在等待持有的锁或先前请求的锁的子级上的独占锁:LOCK+^ a(1,2) 正在等待锁^a(1) |
ModeCount指示阻塞此锁定请求的锁(或锁请求)。这不一定与Reference相同,后者指定当前持有的锁,该锁位于此锁请求正在等待的锁队列的头部。引用不一定指示立即阻止此锁请求的请求锁。
ModeCount可以指示特定引用上特定所有者进程的其他锁状态值。以下是其他ModeCount状态值:
代码 | 描述 |
---|---|
LockPending | 排他锁处于挂起状态。服务器在授予排他锁的过程中可能会出现此状态。不能删除处于锁定挂起状态的锁定。 |
SharePending | 共享锁处于挂起状态。当服务器正在授予共享锁定时,可能会出现此状态。不能删除处于锁定挂起状态的锁定。 |
DelockPending | 正在等待解锁。当服务器正在解锁已持有的锁定时,可能会出现此状态。不能删除处于锁定挂起状态的锁定。 |
Lost | 由于网络重置,锁定丢失。 |
LockZA | 仅限DSM-11:使用ZALLOCATE 命令在DSM-11中建立的排他锁。在Caché中,ZALLOCATE 是LOCK 命令的同义词,并建立与LOCK 相同的ModeCount值。 |
WaitLockZA | 仅限DSM-11:DSM-11中使用ZALLOCATE 命令请求的独占锁正在等待。在Caché中,ZALLOCATE 是LOCK 命令的同义词,并建立与LOCK 相同的ModeCount值。 |
例程列提供所有者进程正在执行的当前行号和例程。
“查看锁定”窗口不能用于解除锁定。
要移除(删除)系统上当前持有的锁,请转到管理门户,选择系统操作,选择锁定,然后选择管理锁。对于所需的进程(所有者),单击“Remove”(删除)或“Remove all Lock for Process”(删除进程的所有锁定)。
删除锁将释放该锁的所有形式:锁的所有增量级别、锁的所有独占版本、独占升级版本和共享版本。删除锁会立即导致应用该锁队列中等待的下一个锁。
还可以使用SYS.Lock.DeleteOneLock()
和SYS.Lock.DeleteAllLock()
方法删除锁定。
删除锁需要写入权限。锁删除记录在审核数据库中(如果启用),而不记录在cconsole.log中。
还可以使用Caché^LOCKTAB
实用程序从%SYS
命名空间中查看和删除(移除)锁。可以以下任一形式执行^LOCKTAB
:
DO ^LOCKTAB
: 允许查看和删锁定。它提供字母代码命令,用于删除单个锁、删除指定进程拥有的所有锁或删除系统上的所有锁。DO View^LOCKTAB
: 允许查看锁定。它不提供删除锁定的选项。请注意,这些实用程序名称区分大小写。
以下终端会话示例显示^LOCKTAB
如何显示当前锁定:
%SYS>DO ^LOCKTAB
Node Name: MYCOMPUTER
LOCK table entries at 07:22AM 12/05/2016
1167408 bytes usable, 1174080 bytes available.
Entry Process X# S# Flg W# Item Locked
1) 4900 1 ^["^^c:\intersystems\cache151\mgr\"]%SYS("CSP","Daemon")
2) 4856 1 ^["^^c:\intersystems\cache151\mgr\"]ISC.LMFMON("License Monitor")
3) 5016 1 ^["^^c:\intersystems\cache151\mgr\"]ISC.Monitor.System
4) 5024 1 ^["^^c:\intersystems\cache151\mgr\"]TASKMGR
5) 6796 1 ^["^^c:\intersystems\cache151\mgr\user\"]a(1)
6) 6796 1e ^["^^c:\intersystems\cache151\mgr\user\"]a(1,1)
7) 6796 2 1 ^["^^c:\intersystems\cache151\mgr\user\"]b(1)Waiters: 3120(XC)
8) 3120 2 ^["^^c:\intersystems\cache151\mgr\user\"]c(1)
9) 2024 1 1 ^["^^c:\intersystems\cache151\mgr\user\"]d(1)
Command=>
在^LOCKTAB
显示中,X#
列列出了持有的排他锁,S#
列列出了持有的共享锁。X#
或S#
数字表示锁定增量计数。后缀“e
”表示锁被定义为正在升级。后缀“D
”表示该锁处于解锁状态;该锁已解锁,但在当前事务结束之前对另一个进程不可用。W#
列列出等待锁定请求的数量。当前事务的结束。如上图所示,进程6796持有递增的共享锁^b(1)
。进程3120具有等待该锁定的一个锁定请求。锁定请求是对^b(1)
的子级(C
)进行排他(X
)锁定。
输入问号(?
)。在Command=>提示符下显示此实用程序的帮助。这包括如何读取此显示屏和删除锁的字母代码命令(如果可用)的进一步说明。
Command=> ?
<RETURN> or "S" to show another screenful of items
"D" or "1" to delete one lock
"J" to delete ALL of one Process' locks
"C" to delete ALL of one SYSTEM's (Computer's) locks
"R" to Restart display
"Q" or "^" to quit
"H" or "?" for help (this text)
"A" to delete ALL locks
In display:
X# Lock count of exclusive lock
S# Lock count of share lock
W# Number of processes are waiting for this lock
In Flg field:
* outgoing (client) lock to another system; may be pending
L Lost lock due to lock reset condition
Z Lock granted by ZA command. The ZA and ZD commands are obsolete,
though the network daemons still grant the locks for remote client
with the ZA lock mode internally.
P Lock Pending, waiting for server to grant the lock
D Delock Pending, waiting for server to release the lock
注意:不能删除处于锁定挂起状态的锁,如FLG
列值所示。
输入q
退出^LOCKTAB
实用程序。
当进程持有排他锁时,它会导致试图获取相同锁的任何其他进程的等待条件,或者导致持有锁的较高级别节点或较低级别节点上的锁。锁定下标全局变量(数组节点)时,务必区分锁定的内容和其他进程可以锁定的内容:
^Student(1,2)
,则只能显式锁定^Student(1,2)
。不能通过释放更高级别的节点(如^Student(1)
)来释放此节点,因为对该节点没有显式锁定。当然,可以以任何顺序显式锁定较高或较低的节点。^student(1)
,因为这样做还会隐式锁定子进程已经显式锁定的子 ^ student(1,2)
。他们无法锁定子^ student(1,2,3)
,因为进程已锁定父^ student(1,2)
。这些其他进程按照指定的顺序在锁队列上等待。它们在锁表中列为等待队列中它们前面指定的最高级别节点。这可能是锁定的节点,也可能是等待锁定的节点。例如:
^student(1,2)
。^student(1)
,但被禁止。这是因为如果进程B锁定了^student(1)
,它也会(隐式地)锁定^student(1,2)
。^student(1,2,3)
,但被禁止。锁表将其列为WaitExclusiveParent ^ student(1,2)
。进程A对^ student(1,2)
持有一个锁,因此对^ student(1,2,3)
具有一个隐式锁。但是,由于进程C在队列中比进程B低,进程C必须等待进程B锁,然后释放^ student(1)
。^student(1,2,3)
。等待锁保持不变。^ student(1)
。等待的锁变了:进程B列为WaitExclusiveExact^student(1)
。进程B正在等待锁定进程A保留的确切锁(^student(1)
)。
进程C被列为WaitExclusiveChild^student(1)
,进程C在队列中比进程B低,因此它正在等待进程B锁定并释放其请求的锁。然后进程C将能够锁定进程B锁的子进程。进程B,依次等待进程A释放^student(1)
。
^ student(1)
。等待锁变回WaitExclusiveParent^student(1,2)
。(与步骤2和3相同的条件。)^ student(1,2)
。等待锁更改为WaitExclusiveParent^student(1,2,3)
。进程B正在等待锁定^student(1)
,当前进程的父进程A锁定^student(1,2,3)
。进程C正在等待进程B锁定然后解锁^student(1)
,进程C请求的^student(1,2,3)
锁定的父进程。^ student(1,2,3)
。进程B锁定^student(1)
。进程C现在被进程B禁止。进程C被列为WaitExclusiveChild^student(1)
。进程C正在等待锁定^student(1,2,3)
,它是当前进程B锁的子进程。数组锁的Caché队列算法是严格按照接收的顺序对同一资源的锁请求进行排队,即使没有直接的资源争用。由于这可能与预期不同,或者与其他数据库上的锁队列实现不同,这里提供了一些说明。
考虑这样的情况:同一全局数组上的三个锁由三个不同的进程请求:
Process A: LOCK ^x(1,1)
Process B: LOCK ^x(1)
Process C: LOCK ^x(1,2)
在这种情况下,进程A将锁定^x(1,1)
在锁定^x(1)
之前,进程B必须等待进程A释放^x(1,1)
。但是C过程呢?授予进程A的锁阻止进程B,但没有持有的锁阻止进程C的锁请求。事实上,进程B正等待显式锁定^x(1)
,从而隐式锁定^x(1,2)
(进程C要锁定的节点)来阻止进程C。在Caché中,进程C必须等待进程B锁定和解锁。
Caché lock排队算法对进程B是最公平的。允许进程C跳转队列的其他数据库实现可以加快进程C的速度,但可能(特别是在有许多job(如进程C)时)导致进程B不可接受的延迟。
注意:这个严格的进程队列算法适用于所有订阅的锁请求。但是,在同时有非预订(lock+^abc
)和预订(lock+^abc(1,1
))等待锁请求时释放非预订锁(如lock-^abc
)的进程是一种特殊情况。在这种情况下,服务于哪个锁请求是不可预测的,并且可能不遵循严格的进程队列。
当释放锁时,ECP客户机可以将锁捐赠给本地服务,而不是其他系统上的服生,以提高性能。允许发生这种情况的次数是有限的,以防止远程锁服务生无法接受的延迟。
持有现有共享锁时请求一个“+
”排他锁可能会导致一种称为“死锁”的情况,因此可能会带来危险。当两个进程分别请求对已被另一个进程锁定为共享锁的锁名称的独占锁时,就会出现这种情况。结果,每个进程在等待另一个进程释放现有共享锁时挂起。
下面的示例显示了这种情况的发生方式(数字表示操作顺序):
进程 A | 进程 B |
---|---|
1. LOCK ^a(1)#"S" | 2. LOCK ^a(1)#"S" |
3. LOCK +^a(1) | 4. LOCK +^a(1) |
等待释放进程B共享锁;死锁。 | 等待进程A释放共享锁;死锁。 |
这是最简单的死锁形式。当进程在所持锁的父节点或子节点上请求锁时,也可能发生死锁。
为了防止死锁,应该请求不带加号的独占锁(从而解锁的共享锁)。在下面的示例中,两个进程在请求独占锁以避免死锁时都会释放它们以前的锁(数字表示操作序列)。注意哪个进程获取独占锁:
进程 A | 进程 B |
---|---|
1. LOCK ^a(1)#"S" | 2.LOCK ^a(1)#"S" |
3.LOCK ^a(1) | 4. LOCK ^a(1) |
释放共享锁,等待进程B共享锁。 | 释放共享锁,立即应用独占锁。 |
另一种避免死锁的方法是按照发出LOCK+
和LOCK-
命令的顺序遵循严格的协议。只要所有进程遵循相同的顺序,就不会出现死锁。一个简单的协议是所有进程按排序顺序应用和释放锁。
为了最小化死锁情况的影响,在使用加号锁时应始终包含timeout参数。例如,LOCK+^a(1):10
。
如果发生死锁,可以使用管理门户或LOCKTAB实用程序删除其中一个有问题的锁来解决它。从管理门户中,打开“锁”窗口,然后为死锁进程选择“移除”选项。