代码之家  ›  专栏  ›  技术社区  ›  Marc Guillot

记录值时忽略冲突

  •  0
  • Marc Guillot  · 技术社区  · 4 周前

    我有一个IIS模块,可以同时处理多个请求。在每个请求的条目中,我想记录上次访问服务的时间:

    update log set last_access = getdate()
    

    但考虑到我有时可以拥有的并发性水平,恐怕我很容易产生冲突。

    由于我的日志不需要毫秒的精度(将查询它以检查服务器在过去X分钟内是否被访问,而不是毫秒),因此如果我的日志面临冲突,它可以完全忽略该日志,什么都不做。

    我最初的想法是在try-catch块中保护我的日志记录,忽略冲突。

    begin try
      update log set last_access = getdate()
    end try
    begin catch
      -- We do nothing and ignore that another request has logged an access at the same time
    end catch
    

    但这是正确的吗?。如果发生冲突,将自动跳转到catch部分,什么都不做,这样我的后端就可以继续正常处理其余的请求了?。

    恐怕它可以等待僵局解除,然后在超时后继续。是否有更好的方法来更新值,同时告诉SQL Server如果另一个会话已在同一时间完成该更新,则忽略该更新?。

    PS:此更新日志将在其自己的事务上运行,而为请求提供服务所需的任何SQL操作都将在单独的事务上执行。

    非常感谢。

    1 回复  |  直到 4 周前
        1
  •  2
  •   Joel Coehoorn    3 周前

    但考虑到我有时可以拥有的并发性水平,恐怕我很容易产生冲突。

    因为我的日志不需要毫秒精度

    考虑到这两件事,我相信你想得太多了,真的不需要担心。

    确实有可能出现并发或竞争情况,这样以后的访问可能会在较早的访问之前推送其更新。然而,无论如何,我们在这里谈论的最多只有几毫秒。由于这种精确度并不重要,你会没事的。

    另一个问题是同时进行多个更新导致的锁定或阻塞。但再一次,看起来你想得太多了。关系数据库通常是 好的 在数据库中处理这种情况。它们将以适当的方式锁定,以确保两个更新都发生,并且以原子和高性能的方式进行,即稍后的更新获胜,为给定的行/列设置最终值。这种事情是关系数据库最初创建的主要原因之一。

    有可能产生死锁,例如事务A正在等待事务B的锁,而事务B正在等待事务A的锁,并且它们永远被卡住。更长、更复杂的链/环也是可能的。但这通常涉及以不同顺序锁定多个事物的交易。这里不会发生这种情况。

    我可能会做的一件事就是确保你有一个 WHERE 子句,即使表中只有一行。


    回顾我开始写原始答案以来发布的评论,我看到毕竟有多行在发挥作用。这确实会产生(微小的!)死锁的可能性。你可以做四件事来帮忙。选择其中任何一个,只要始终如一地完成,就可以保证避免死锁。

    1. 如果在一个事务中发出多个更新语句(每个更改的行有不同的语句),请始终以相同的顺序发出它们。
    2. 如果发出多个更新语句(每行更改一个不同的语句),但行顺序需要改变,请在单独的事务中发出它们。(当然,这样我们就失去了保证的原子性)。
    3. 通过WHERE子句发出一条update语句,以正确的行为目标,其中所有行都使用相同的行更新同一列 getdate() 价值,
    4. 发出一个update语句,通过WHERE子句定位正确的行,并在单个update语句中使用CASE表达式来决定为哪些列设置什么值。CASE表达式可用于为其自身分配列值,以便在不需要更改的情况下保留先前的值。请注意,这也可能有其他副作用,所以这通常不是我的第一选择。