【问题标题】:Atomic update with joins in SQL使用 SQL 中的连接进行原子更新
【发布时间】:2017-07-16 22:39:06
【问题描述】:

我有一个跟踪“签出”对象的表,但对象存在于其他各种表中。目标是允许用户签出符合他们标准的对象,这样一个给定的对象只能被签出一次(即没有两个用户应该能够签出同一个对象)。在某些情况下,一个对象可能跨越多个表,需要使用连接来检查所有用户的标准(如果有问题的话)。

这是一个非常简单的示例查询(希望您可以推断出架构):

update top (1) Tracker
set IsCheckedOut = 1
  from Tracker t
  join Object o on t.ObjectId = o.Id
  join Property p on p.ObjectId = o.Id
  where t.IsCheckedOut = 0
  and o.SomePropertyColumn = 'blah'
  and p.SomeOtherPropertyColumn = 42

由于from 子查询,我怀疑这个查询不是原子的,因此两个用户同时请求相同风格的对象最终可能会签出同一个对象。

这是真的吗?如果是这样,我该如何解决这个问题?

我考虑过添加一个output DELETED.* 子句并让用户在IsCheckedOut 列的返回值为1 时重试他们的查询,我认为这会起作用(如果我错了,请纠正我)...但是我想得到一些用户不必担心重试的东西。

编辑

有关详细说明,请参阅下面 SqlZim 的答案,但对于这个简单的情况,我可以直接将提示添加到上面发布的查询中:

update top (1) Tracker
set IsCheckedOut = 1
  from Tracker t (updlock, rowlock, readpast)
  join Object o on t.ObjectId = o.Id
  join Property p on p.ObjectId = o.Id
  where t.IsCheckedOut = 0
  and o.SomePropertyColumn = 'blah'
  and p.SomeOtherPropertyColumn = 42

【问题讨论】:

  • 介意用给定的例子更具体地充实一下吗?如果结果是正确的,我会接受它作为答案。

标签: sql sql-server join atomic sql-update


【解决方案1】:

使用事务和一些表hints 进行锁定,我们可以只抓取一行并保持它进行更新。

declare @TrackerId int;

begin tran;

select top 1 @TrackerId = TrackerId
  from Tracker t with (updlock, rowlock, readpast)
    inner join Object o on t.ObjectId = o.Id
    inner join Property p on p.ObjectId = o.Id;
  where t.IsCheckedOut = 0
    and o.SomePropertyColumn = 'blah'
    and p.SomeOtherPropertyColumn = 42;

if @TrackerId is not null 
begin;
update Tracker
  set IsCheckedOut = 1
  where TrackerId = @TrackerId;
end;

commit tran
  • updlock 在来自我们的select 的行上放置一个更新锁。其他事务将无法更新或删除该行,但他们可以选择它,但是一个并发选择试图获取该行上的更新锁(即从具有相同搜索条件的不同进程中再次运行此过程)将无法选择此特定行,但它可以选择并锁定下一行,因为我们也在使用readpast

  • rowlock 尝试只锁定我们要更新的特定行,而不是页面或表锁定。

  • readpast 跳过具有行级锁的行。

参考资料:


使用公用表表达式替换一步代码:

begin tran;

  with cte as (
    select top 1 
        t.*
      from Tracker t with (updlock, rowlock, readpast)
        inner join Object o 
          on t.ObjectId = o.Id
        inner join Property p 
          on p.ObjectId = o.Id;
      where t.IsCheckedOut = 0
        and o.SomePropertyColumn = 'blah'
        and p.SomeOtherPropertyColumn = 42
      --order by TrackerId asc /* optional order by */
  )
  update cte
    set IsCheckedOut = 1
    output inserted.*;

commit tran;

【讨论】:

  • 按原样将这些提示添加到我的from 查询并保持整体查询不变(即不要将其重写为两个单独的语句)是否正确?
  • 哦,我忘了提,无论如何我都需要一个output 子句,因为我还想将签出的行返回给用户
  • @gzak 是的,您也可以使用 cte 编写它
  • 我的想法更简单,只需准确获取我发布的查询并直接在 from 行中添加提示,无需任何其他更改 - 这仍然有效吗?
  • 不是每个语句都隐含在事务中吗?还是必须是非默认交易类型?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-10
  • 2013-01-04
  • 2023-01-18
  • 2021-06-07
  • 2014-12-29
相关资源
最近更新 更多