【问题标题】:Attempting to lock a row in SQL with a 'Lock' column尝试使用“锁定”列锁定 SQL 中的行
【发布时间】:2018-01-19 08:26:38
【问题描述】:

我正在尝试锁定数据库中的一行,使用标识锁定持有者的 guid 和指定锁定有效时间的到期日期(以防进程崩溃,并且无法释放锁)。到目前为止,这是我想出的:

UPDATE Foo
SET
    Lock = @Lock,
    LockExpiry = DATEADD(MILLISECOND, @Duration, GETDATE())
WHERE Id = (
    SELECT TOP 1 Id
    FROM Foo
    WHERE (Lock IS NULL OR LockExpiry < GETDATE())
    ORDER BY LockExpiry
);

SELECT TOP 1
FROM Foo
WHERE (Lock = @Lock AND LockExpiry > GETDATE())

但是,我在项目中添加了一些日志记录,似乎锁定有时会被其他进程覆盖。我怀疑这里可能存在竞争条件:

  • 线程 A 选择顶部等待更新的 Foo,并获取 Foo #3
  • 线程 B 选择顶部等待更新的 Foo,并获取 Foo #3
  • 线程 B 将 Foo #3 的锁定 ID 设置为某个 guid 'xxxx'
  • 线程 B 选择它持有锁的顶部 Foo,并返回 Foo #3
  • 线程 A 将 Foo #3 的锁定 ID 设置为某个 guid 'yyyy'
  • 线程 A 选择它持有锁的顶部 Foo,并返回 Foo #3

我如何判断这是否正在发生?如果可能,我该如何预防?

编辑

另外,有没有更简单的方法呢?我需要锁定该行,并让锁定在多个事务中持续存在,甚至可能是多个连接。

【问题讨论】:

  • 墨菲定律:如果某件事可能失败,它就会失败。如果存在锁定机制是有原因的,因为您无法中继一行中的数据以确保锁定。
  • @Gusman 我正在使用 EntityFramework 的 ExecuteSqlCommandAsync,它会自动将语句包装在 BEGIN TRANSACTIONEND TRANSACTION 中。我可以在事务中调用SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 吗?
  • 从这里:msdn.microsoft.com/en-us/library/dn220055(v=vs.113).aspx“如果没有现有的本地事务,将使用新事务来执行命令”所以,您可以先创建事务,然后执行查询。
  • 好的,现在就测试一下。如果可行,我将投票关闭重复。

标签: sql-server multithreading locking


【解决方案1】:

我已经设法通过使UPDATE 语句检查与选择相同的条件来解决此问题:

-- Only update the first row, because the WHERE clause can now match multiple
UPDATE TOP (1) Foo
SET
    Lock = @Lock,
    LockExpiry = DATEADD(MILLISECOND, @Duration, GETDATE())
-- Make sure that the check for the lock and the update are part of the same operation
WHERE (Lock IS NULL OR LockExpiry < GETDATE())
AND Id IN (
    -- The first Foo may have been locked by another thread by the time the UPDATE
    -- statement runs, so return multiple entries to try
    SELECT TOP 10 Id
    FROM Foo
    WHERE (Lock IS NULL OR LockExpiry < GETDATE())
    ORDER BY LockExpiry
);

SELECT TOP 1
FROM Foo
WHERE (Lock = @Lock AND LockExpiry > GETDATE())

请注意,这可能会返回假阴性,即如果 11 个线程尝试同时获取 Foo,其中一个可能不会成功,即使当时有足够的 Foo 可用。

此外,这个解决方案现在意味着我们不一定会得到最古老的杰出的 Foo,但我们会得到一个旧的,而且很有可能会成为最年长的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-24
    • 1970-01-01
    • 2016-04-10
    • 2013-09-23
    • 2017-04-05
    • 1970-01-01
    • 2018-03-26
    相关资源
    最近更新 更多