【问题标题】:Correct way to take a exclusive lock获取排他锁的正确方法
【发布时间】:2012-11-17 23:50:06
【问题描述】:

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

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

我很想在行级别而不是表级别上做一个排他锁,但是what I have read so far 说如果其他事务在READCOMMITED 隔离级别上运行,我不能做with (XLOCK, ROWLOCK, HOLDLOCK)(这是为了我)。

我是否正确地使用了表级独占锁,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

【问题讨论】:

  • 我想弄清楚为什么你需要一个独占锁。其他人可能会插入记录吗?其他人更新记录?您是否担心其他人对数据的看法不一致?
  • @Laurence 我担心其他人的视图状态不一致。我正在尝试纠正一个影响一小部分客户的错误,但是该纠正过程在几个表中留下了几个相互依赖的行(我实际上将锁定 5 个表,但将我的代码示例简化为一个表)在期间处于不一致状态修正过程。不一致是每个客户端隔离的,但是在“更正”过程中对一个客户端执行SELECT SUM(ColA) FROM trnHistory 会返回不正确的值。所以我需要采取排他锁来防止读取。
  • 我不明白为什么交易不能保护你免受这种情况的影响,除非你有人在做 read_uncommitted。
  • @Laurence 我知道确实如此,我的问题是:“我是否正确使用了表级排他锁,Server 2008 R2 中是否有任何方法可以让行级排他锁按我想要的方式工作到不修改数据库上运行的其他查询?”。正如我之前所说,我正在使用 5 个数据库,我想同时锁定所有 5 个数据库,而不是第一次触摸每个数据库以保持一致性。我将更新我的代码示例以显示其他表,因为您让我意识到我确实犯了一个错误。
  • 我可能很密集,但是如果您只是担心其他读者,那么交易还不够吗?开始事务,使不一致,使一致,提交事务。没有人看到中间的部分,除非他们说他们不介意不一致的数据。如果您担心其他作家,那么我认为行 xlocks 会很好。不过,您确实曾经提到过 5 个数据库。分布式事务可能会出现问题。

标签: sql sql-server sql-server-2008-r2 locking


【解决方案1】:

我不敢相信XLOCK 不会阻止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 未被阻止。

我也尝试过使用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,在某些情况下,一个事务可以独占锁定一行,而另一个事务可以读取同一行!这可能在存在索引的情况下发生。一个事务可能会在非聚集索引 IX_T_SomeCol 上进行 X 锁定,而另一个事务可能会愉快地读取聚集索引 PK_T

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

【讨论】:

  • 我发现只有当我在读取 sql 时使用 REPEATABLE READ 或 SERIALIZABLE 隔离级别时才会阻塞它(如果存在未提交的 rowlock+ xlock)
  • 是的,只有隔离级别 REPEATABLE READ 或 SERIALIZABLE 可以阻塞会话 2,因为即使会话获得了 XLOCK,即使你没有提交事务,会话 1 在读取完成后已经释放 XLOCK .它不再持有它。
  • @Xin 由于HOLDLOCK,它确实持有它。这与SERIALIZABLE 相同,并持有所有锁直到事务结束。
  • 我没有看到这种行为。在 SQL Server 2005 上试过这个,它像我预期的那样阻塞,不知道你是如何设法让一个事务用 XLOCK 读取一行的,但是锁兼容性矩阵很清楚 X 与 S 不兼容(即你不能当行上存在排他锁时读取已提交)。
  • @Jim 我曾经读过一篇关于它的开发者的权威博客文章(Paul Randall?!)。使 RC 隔离 为每一行使用 S 锁是一种优化,这是昂贵的。也许古 2005 还没有这个。很高兴知道。我当然在 >=2008 上进行了测试。
【解决方案2】:

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

Begin Transaction

  Make Data Inconsistent

  Make Data Consistent

Commit Transaction

应该没问题。唯一会看到不一致数据的会话是那些使用nolockRead Uncommitted 的会话,或者那些希望在不使用Repeatable RowsSerializable 的情况下进行多次一致读取的会话。

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

【讨论】:

    猜你喜欢
    • 2015-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-26
    相关资源
    最近更新 更多