13

这需要一些解释。我所做的是在 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 并尝试同时确认它吗?我可以(或者我应该)实现某种锁定以防止另一个存储过程确认另一个存储过程正在查询的消息吗?

哇,这有任何意义吗?有点难以表达...

4

7 回答 7

7

像这样的东西

--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
...
...
于 2008-09-11T21:09:57.000 回答
2

这似乎是一种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 nulltop,update而不是在 上的子选择UnacknowledgedDemands),则没有必要。我希望即使它是不必要的,它也是无害的。

请注意,这OUTPUT是在 SQL Server 2005 及更高版本中。这就是你所说的你正在使用的,但如果需要与老年 SQL Server 2000 安装兼容,你会想要另一种方式。

于 2011-07-19T11:25:43.490 回答
1

@基尔霍夫:

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

编辑:此外,SQL 不一定会锁定整个表 - 它可以只锁定必要的行。有关SQL Server 中锁定的概述,请参见此处。

于 2008-09-11T21:06:23.327 回答
1

而不是显式锁定,通常由 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 */

您锁定的越少 - 您拥有的并发性越高。

于 2008-09-24T12:59:45.607 回答
0

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

于 2008-09-11T21:04:32.723 回答
0

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

于 2008-09-11T21:11:06.763 回答
-1

您想将代码包装在事务中,然后 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
...
于 2008-09-11T21:01:47.907 回答