这需要一些解释。我所做的是在 SQL Server 2005 中创建一个特定的自定义消息队列。我有一个包含消息的表,其中包含确认和完成的时间戳。调用者执行以获取队列中下一条消息的存储过程也会确认该消息。到目前为止,一切都很好。好吧,如果系统正在经历大量事务(每分钟数千个),那么是否有可能在另一个存储过程的执行已准备好自己确认消息的同时,一条消息被另一个存储过程的执行所确认?让我通过在存储过程中显示我的 SQL 代码来提供帮助:

--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

--Select the entire message
...
...

在上面的代码中,是否不能同时运行的另一个存储过程获取相同的 id 并尝试同时确认它?我可以(或者应该)实现某种锁定以防止另一个存储过程确认另一个存储过程正在查询的消息吗?

哇,这一切有道理吗?有点难以用言语来表达...

有帮助吗?

解决方案

像这样的东西

--Grab the next message id
begin tran
declare @MessageId uniqueidentifier
select top 1 @MessageId =   ActionMessageId from UnacknowledgedDemands with(holdlock, updlock);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

-- some error checking
commit tran

--Select the entire message
...
...

其他提示

这似乎是一种情况 OUTPUT 可能有用:

-- Acknowledge and grab the next message
declare @message table (
    -- ...your `ActionMessages` columns here...
)
update ActionMessages
set    AcknowledgedTime = getdate()
output INSERTED.* into @message
where  ActionMessageId in (select top(1) ActionMessageId from UnacknowledgedDemands)
  and  AcknowledgedTime is null

-- Use the data in @message, which will have zero or one rows assuming
-- `ActionMessageId` uniquely identifies a row (strongly implied in your question)
...
...

在那里,我们在同一操作中更新并获取行,这告诉查询优化器 确切地 我们正在做的事情,允许它选择最细粒度的锁,并在尽可能短的时间内维护它。(虽然列前缀是 INSERTED, OUTPUT 就像触发器一样,用以下形式表示 UPDATE 就像删除行并插入新行一样。)

我需要更多关于你的信息 ActionMessagesUnacknowledgedDemands 表(视图/TVF/其他),更不用说对 SQL Server 自动锁定有更多的了解,来说明是否 and AcknowledgedTime is null 条款是必要的。它的作用是防止子选择和更新之间的竞争条件。我确信如果我们选择的话就没有必要 ActionMessages 本身(例如, where AcknowledgedTime is null 与一个 topupdate, ,而不是子选择 UnacknowledgedDemands)。我希望即使没有必要,也是无害的。

注意 OUTPUT 适用于 SQL Server 2005 及以上版本。这就是您所说的您正在使用的方式,但如果需要与老年 SQL Server 2000 安装兼容,您可能会想采用另一种方式。

@基尔霍夫:

整个 SQL 批处理在执行之前都会被解析,因此 SQL 知道您将对表进行更新并从中进行选择。

编辑:此外,SQL 不一定会锁定整个表 - 它可能只锁定必要的行。看 这里 有关 SQL Server 中锁定的概述。

为什么不直接尝试这种方法,而不是显式锁定(SQL Server 通常将显式锁定升级到比预期更高的粒度):

declare @MessageId uniqueidentifier
select top 1 @MessageId = ActionMessageId from UnacknowledgedDemands

update ActionMessages
  set AcknowledgedTime = getdate()
  where ActionMessageId = @MessageId and AcknowledgedTime is null

if @@rowcount > 0
  /* acknoweldge succeeded */
else
  /* concurrent query acknowledged message before us,
     go back and try another one */

锁定越少,并发性就越高。

你真的应该一件一件地处理事情吗?难道您不应该让 SQL Server 用今天的日期确认所有未确认的消息并返回它们吗?(当然,所有这些也在交易中)

阅读有关 SQL Server 选择锁定的更多信息 这里这里. 。SQL Server 能够在选择上调用表锁。事务期间表不会发生任何变化。当事务完成时,任何插入或更新都将自行解决。

您希望将代码包装在事务中,然后 SQL Server 将处理锁定适当的行或表。

begin transaction

--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

commit transaction

--Select the entire message
...
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top