6

我正在编写一个程序来协调实时数据库上的最终交易。我正在做的工作不能作为一个集合操作来完成,所以我使用了两个嵌套游标。

在为每个客户端进行协调时,我需要在事务表上使用排他锁,但我想释放锁并让其他人在我处理的每个客户端之间运行他们的查询。

我很想在行级别而不是表级别上做一个排他锁,但是到目前为止我所读到with (XLOCK, ROWLOCK, HOLDLOCK)的内容表明,如果其他事务在READCOMMITED隔离级别上运行(这对我来说),我就不能这样做。

我是否正确使用了表级独占锁,Server 2008 R2 中是否有任何方法可以让行级独占锁按照我想要的方式工作,而无需修改数据库上运行的其他查询?

declare client_cursor cursor local forward_only for 
     select distinct CLIENT_GUID from trnHistory
open client_cursor

declare @ClientGuid uniqueidentifier
declare @TransGuid uniqueidentifier

fetch next from client_cursor into @ClientGuid
WHILE (@@FETCH_STATUS <> -1)
BEGIN
    IF (@@FETCH_STATUS <> -2)
    BEGIN
        begin tran

        declare @temp int

        --The following row will not work if the other connections are running READCOMMITED isolation level
        --select @temp = 1 
    --from trnHistory with (XLOCK, ROWLOCK, HOLDLOCK) 
    --left join trnCB with (XLOCK, ROWLOCK, HOLDLOCK) on trnHistory.TRANS_GUID = trnCB.TRANS_GUID
    --left join trnClients with (XLOCK, ROWLOCK, HOLDLOCK) on trnHistory.TRANS_GUID = trnClients.TRANS_GUID
    --(Snip) --Other tables that will be "touched" during the reconcile
    --where trnHistory.CLIENT_GUID = @ClientGuid

        --Works allways but locks whole table.
    select top 1 @temp = 1 from trnHistory with (XLOCK, TABLOCK) 
    select top 1 @temp = 1 from trnCB with (XLOCK, TABLOCK)
    select top 1 @temp = 1 from trnClients with (XLOCK, TABLOCK)
    --(Snip) --Other tables that will be "touched" during the reconcile

        declare trans_cursor cursor local forward_only for 
                select TRANS_GUID from trnHistory where CLIENT_GUID = @ClientGuid order by TRANS_NUMBER
        open trans_cursor

        fetch next from trans_cursor into @TransGuid
        WHILE (@@FETCH_STATUS <> -1)
        BEGIN
            IF (@@FETCH_STATUS <> -2)
            BEGIN

                --Do Work here

            END
            fetch next from trans_cursor into @TransGuid
        END

        close trans_cursor
        deallocate trans_cursor

            --commit the transaction and release the lock, this allows other 
            -- connections to get a few queries in while it is safe to read.
        commit tran
    END

    fetch next from client_cursor into @ClientGuid
END 

close client_cursor
deallocate client_cursor
4

2 回答 2

14

我不敢相信 anXLOCK不会阻止并发阅读器,read committed所以我只是复制了它:这是真的。脚本:

第 1 节:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN

SELECT * FROM T WITH (ROWLOCK, XLOCK, HOLDLOCK /*PAGLOCK, TABLOCKX*/) WHERE ID = 123

第 2 节:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN

SELECT * FROM T WHERE ID = 123

插入您手头的一些表名。会话 2 未被阻止。

我也尝试过使用 a PAGLOCK,但这也没有用。接下来我尝试了一个TABLOCKX,但也没有用!

所以你的基于表锁的策略不起作用。我认为你必须修改读者,以便他们要么

  1. 使用快照隔离来获得一致的视图(在任何写入之前)
  2. 使用更高的隔离级别被作者阻止

当然有一个讨厌的解决方法来真正地锁定表:改变它的模式。这将采用Sch-M与基本上对表的任何访问相冲突的锁。它甚至包含一些元数据读取操作。它可能看起来像这样:

--just change *any* setting in an idempotent way
ALTER TABLE T SET (LOCK_ESCALATION = AUTO)

我对此进行了测试。


SQL Server 是否正确不遵守XLOCK?或者这是产品的缺陷?我认为这是正确的,因为它符合READ COMMITTED. 此外,即使SERIALIZABLE在某些情况下,一个事务可以独占锁定一行,而另一个事务可以读取同一行!这可能在存在索引的情况下发生。一个事务可能会在非聚集索引上进行 X 锁定,IX_T_SomeCol而另一个事务可能会愉快地读取聚集索引PK_T

因此,即使存在排他锁定,事务也可以独立执行实际上是很正常的。

于 2012-11-18T00:55:19.927 回答
5

如果你只是担心其他读者,那么你不应该需要排他锁,模式

Begin Transaction

  Make Data Inconsistent

  Make Data Consistent

Commit Transaction

应该没事。唯一会看到不一致数据的会话是那些使用nolockor的会话Read Uncommitted,或者那些期望在不使用Repeatable Rowsor的情况下进行多次一致读取的会话Serializable

在回答这个问题时,在我看来,获取排他锁的正确方法是安排事情,以便引擎为你做这件事。

于 2012-11-18T12:15:20.010 回答