SQL Server SELECT / UPDATE存储过程的怪异

时间:2009-02-18 19:25:02

标签: sql-server tsql stored-procedures

我有一张桌子,我用作工作队列。本质上,它由主键,一段数据和状态标志(已处理/未处理)组成。我有多个进程试图抓住下一个未处理的行,所以我需要确保他们观察到正确的锁定并更新语义以避免竞争条件的恶化。为此,我已经定义了一个可以调用的存储过程:

CREATE PROCEDURE get_from_q
AS
DECLARE @queueid INT;
BEGIN TRANSACTION TRAN1;

SELECT TOP 1 
    @queueid = id 
FROM 
    MSG_Q WITH (updlock, readpast) 
WHERE 
    MSG_Q.status=0;

SELECT TOP 1 * 
FROM
    MSG_Q 
WHERE 
    MSG_Q.id=@queueid;

UPDATE MSG_Q 
SET status=1 
WHERE id=@queueid;

COMMIT TRANSACTION TRAN1;

请注意使用“WITH(updlock,readpast)”来确保我锁定目标行并忽略已经类似锁定的行。

现在,程序如上所列,这很好。然而,当我把它放在一起时,我发现如果第二个SELECT和UPDATE按顺序颠倒(即首先是UPDATE然后是SELECT),我根本就没有数据。不,第二个SELECT是在最终COMMIT之前还是之后无关紧要。

我的问题是为什么第二个SELECT和UPDATE的顺序有所不同。我怀疑那里有一些微妙的东西,我不明白,我担心以后会咬我。

任何提示?

4 个答案:

答案 0 :(得分:2)

默认情况下,事务是READ COMMITTED:

“指定在读取数据时保留共享锁以避免脏读,但可以在事务结束之前更改数据,从而导致不可重复的读取或幻像数据。此选项是SQL Server的默认值。 “

http://msdn.microsoft.com/en-us/library/aa259216.aspx

我认为你在选择中没有得到任何东西,因为记录仍然标记为脏。您必须更改事务隔离级别OR,我所做的是先执行更新然后读取记录,但要执行此操作,您必须使用唯一值标记记录(我对批处理使用getdate()但GUID将是您可能想要使用的)。

答案 1 :(得分:1)

虽然不是直接在这里回答你的问题,而是重新发明轮子并让自己的生活变得困难,除非你当然喜欢它;-),我建议你看一下使用SQL Server Service Broker。

它提供了使用队列等的现有框架。

了解更多信息。

Service Broker Link

现在回到问题,我无法复制您的问题,因为您将看到如果您执行下面的代码,无论选择/更新语句的顺序如何都会返回数据。

那么你上面的例子。

create table #MSG_Q
(id int identity(1,1) primary key,status int)
insert into #MSG_Q select 0

DECLARE @queueid INT
BEGIN TRANSACTION TRAN1
SELECT TOP 1 @queueid = id FROM #MSG_Q WITH (updlock, readpast) WHERE #MSG_Q.status=0
UPDATE #MSG_Q SET status=1 WHERE id=@queueid
SELECT TOP 1 * FROM #MSG_Q WHERE #MSG_Q.id=@queueid
COMMIT TRANSACTION TRAN1

select * from #MSG_Q
drop table #MSG_Q

返回结果(1,1)和(1,1)

现在交换语句顺序。

create table #MSG_Q
(id int identity(1,1) primary key,status int)    
insert into #MSG_Q select 0

DECLARE @queueid INT
BEGIN TRANSACTION TRAN1
SELECT TOP 1 @queueid = id FROM #MSG_Q WITH (updlock, readpast) WHERE #MSG_Q.status=0
SELECT TOP 1 * FROM #MSG_Q WHERE #MSG_Q.id=@queueid
UPDATE #MSG_Q SET status=1 WHERE id=@queueid
COMMIT TRANSACTION TRAN1

select * from #MSG_Q
drop table #MSG_Q

结果:(1,0),(1,1)符合预期。

也许您可以进一步限定您的问题?

答案 2 :(得分:1)

更多的实验让我得出结论,我正在追逐一只红鲱鱼,这是由我用来执行存储过程的工具带来的。我最初使用的是DBVisualizer(免费版)和Netbeans,他们似乎都对结果的格式感到困惑。 DBVisualizer建议我得到多个结果集,并且免费版不能处理它。

从那时起,我抓住了免费的MS SQL Server Management Studio Express,完美无缺。对于那些感兴趣的人,SMSE的URL在这里:

MS SQL Server SMSE

不要忘记安装MSXML6服务包:

MSXML Service Pack 1

所以,在这种情况下完全是我的坏事。 : - (

非常感谢和赞赏你们的答案。你帮助我确认我正在做的事情应该有效,这导致我必须做出改变才能真正“解决”这个问题。非常感谢!

答案 3 :(得分:0)

还有一点 - 包括存储过程中的“SET NOCOUNT ON”,修复了所有ODBC客户端的内容。显然,第一个选择的行计数会让ODBC客户端感到困惑,并且告诉SQL Server不返回该值会使事情完美地运行......