【问题标题】:SQL Server SELECT/UPDATE Stored Procedure WeirdnessSQL Server SELECT/UPDATE 存储过程怪异
【发布时间】:2009-02-18 19:25:02
【问题描述】:

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

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 的顺序会有所不同。我怀疑那里发生了一些我不明白的微妙事情,我担心它以后会咬我。

有什么提示吗?

【问题讨论】:

    标签: sql-server tsql stored-procedures


    【解决方案1】:

    默认情况下事务是 READ COMMITTED :

    "指定在读取数据时持有共享锁以避免脏读,但可以在事务结束前更改数据,导致不可重复读取或幻像数据。该选项是SQL Server默认的。 "

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

    我认为您在选择中一无所获,因为该记录仍被标记为脏。您必须更改事务隔离级别或者,我所做的是先进行更新然后读取记录,但要做到这一点,您必须使用唯一值标记记录(我使用 getdate() 进行批处理但 GUID 可能是您想要使用的)。

    【讨论】:

      【解决方案2】:

      虽然这里没有直接回答你的问题,而不是重新发明轮子,让自己的生活变得困难,除非你当然喜欢它;-),我建议你看看使用 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) 符合预期。

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

      【讨论】:

      • 嗯,链接似乎已损坏,但这有效:msdn.microsoft.com/en-us/library/ms345108.aspx 另外,我只能使用与 SQL Server 数据库的 ODBC 连接,所以我不确定 Service Broker 是否适用我。不过看起来完美....
      【解决方案3】:

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

      从那时起,我使用了免费的 MS SQL Server Management Studio Express,一切都运行良好。对于那些感兴趣的人,SMSE 的 URL 在这里:

      MS SQL Server SMSE

      不要忘记安装 MSXML6 服务包:

      MSXML Service Pack 1

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

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

      【讨论】:

        【解决方案4】:

        还有一点——在存储过程中包含一个“SET NOCOUNT ON”,为所有 ODBC 客户端修复了问题。显然,第一次选择的行数混淆了 ODBC 客户端,并且告诉 SQL Server 不返回该值会使事情完美地工作......

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-19
          相关资源
          最近更新 更多