UPDATE 锁并不是一种单独的锁类型,倒是有点像是SHARED和EXCLUSIVE锁的混合。并且可能与你认为的不同,UPDATE 锁不是由UPDATE操作获取的。 当SQL Server执行一个数据修改操作,但是需要首先执行一个检索来查找需要修改的资源时,事务会获取这种类型的锁。
当SQL Server搜索时,它不需要获取EXCLUSIVE锁,只有在找到要更改数据时,才需要EXCLUSIVE锁。通常情况下,如果SQL Server进程只是搜索数据,它会在所访问到的每个资源上获取SHARED锁,然后确定是否已经找到了正在搜索的数据。但是,如果要搜索的数据是用来修改的话,SQL Server启用SHARED锁则存在潜在问题。例如,两个进程都是寻找相同的资源(如Customers表中同一客户行)进行修改,使用不同的访问的路径,并且它们在同一时间达到所需的资源。如果它们都在检索的数据上获取SHARED锁,它们都可以同时锁定要修改的资源,但在它们进行修改前需要将锁转换为EXCLUSIVE锁。 由于另一个进程具有了SHARED锁,则不会生成EXCLUSIVE锁。 每个进程都具有一个SHARED锁,并且每个都尝试将其转换为EXCLUSIVE的锁,但是都会由于另外一个进程的存在,这两个尝试都不会成功。这是一种死锁情况,叫做“转换死锁”。
UPDATE 锁是一种死锁避免机制。如果SQL Server使用UPDATE锁,则死锁将不会发生。 如果SQL Server进程开始了一个最终要修改数据的搜索操作,它获取UPDATE锁,直到找到要修改的数据。 UPDATE 锁与SHARED锁兼容,但与EXCLUSIVE锁或其他UPDATE锁不兼容。 因此,如果两个进程正在寻找相同的数据资源,则第一个到达的进程会获取到UPDATE锁,然后在第二个进程无法取得任何锁定,并且将等待第一个进程处理完成。由于第一个进程没有被阻塞,它可以将其UPDATE锁转换为EXCLUSIVE锁,并完成事务处理后释放锁,然后在第二个进程进行其他修改。
在 sys.dm_tran_locks 视图中,request_mode值为U的代表 UPDATE 锁。
来看一下UPDATE锁:
[我正在使用旧的示例数据库 pubs。 如果要尝试下面的代码可以单击这里下载]。
-- Close all existing connections and start a new one
-- Step 1:
USE pubs;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRAN;
UPDATE authors
SET contract = 0
WHERE au_lname = 'Ringer' ;
---- Step 2: Open a second connection window uncomment once,
-- so the ROLLBACK is still commented. Execute…
--USE pubs;
--SET TRANSACTION ISOLATION LEVEL READ COMMITTED ;
--BEGIN TRAN;
--UPDATE authors
--SET city = 'Provo'
--WHERE state = 'UT';
-- You should be blocked.
---- ROLLBACK TRAN;
-- Step 3: Go back to the first connection window and run the following
SELECT request_session_id AS session_id, DB_NAME(resource_database_id) AS [database],
request_mode AS mode, resource_type as [type],
resource_associated_entity_id AS entity,
resource_description, request_status AS status
FROM sys.dm_tran_locks;
COMMIT TRAN;
你将会得到类似下面的输出结果:
注意KEY为WAIT状态的U锁,它具有与第一个连接所GRANT的KEY相同的资源描述。现在COMMIT或ROLLBACK第一个连接,你会看到第二个连接所等待的KEY上获得X锁,还有在另一个KEY上的X锁。
我提及UPDATE锁的目的是说明“修改数据的意向”,因此你可能会认为UPDATE的锁是类似于INIENT锁。 不是这样的,UPDATE锁指示一个更改锁锁模式的意向,而INTENT锁指示一个更改锁粒度的意向。