【问题标题】:Is this stored procedure thread-safe? (or whatever the equiv is on SQL Server)这个存储过程是线程安全的吗? (或 SQL Server 上的任何等价物)
【发布时间】:2010-12-01 18:54:21
【问题描述】:

在 SO 上其他人的帮助下,今天早上我敲定了几个表和存储过程,因为我离数据库程序员还很远。

有人介意看看这个并告诉我它是否是线程安全的吗?我想这可能不是 DBA/DB 开发人员使用的术语,但我希望你明白:基本上,如果这个 sp 正在执行并且另一个同时出现会发生什么?一个会干扰另一个吗?这甚至是 SQL/SP 中的问题吗?

CREATE PROCEDURE [dbo].[usp_NewTicketNumber]
    @ticketNumber int OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    INSERT INTO [TEST_Db42].[dbo].[TicketNumber]
               ([CreatedDateTime], [CreatedBy])
         VALUES
                (GETDATE(), SUSER_SNAME())
    SELECT @ticketNumber = IDENT_CURRENT('[dbo].[TicketNumber]');
    RETURN 0;
END

【问题讨论】:

  • 我完全同意 gbn 的回答,但想补充一点,您可以自己轻松解决这个问题 - 您可以在一个循环中从两个或多个连接同时运行您的存储过程 mnay 次 (>1mln ),然后自己看看。

标签: sql sql-server sql-server-2005 tsql thread-safety


【解决方案1】:

您可能不想使用 IDENT_CURRENT - 这会返回在任何会话和任何范围内在相关表上生成的最新身份。如果其他人在错误的时间插入,您将获得他们的 id!

如果您想获取刚刚执行的插入生成的标识,那么最好使用OUTPUT 子句来检索它。过去通常为此使用 SCOPE_IDENTITY(),但在并行执行计划下会出现问题。

线程安全的主要 SQL 等效项是执行多个导致意外或不良行为的语句时。我能想到的两种主要类型的此类行为是锁定(特别是死锁)和并发问题。

当一个语句阻止其他语句访问它正在处理的行时,就会出现锁定问题。这可能会影响性能,在最坏的情况下,两条语句会进行无法协调的更改并发生死锁,从而导致一条语句终止。

但是,除非涉及其他事情(例如数据库事务),否则像您所拥有的那样的简单插入不应该导致锁定。

并发问题(描述得很差)是由对数据库记录的一组更改覆盖对相同记录的其他更改引起的。同样,插入记录时这应该不是问题。

【讨论】:

  • -1 用于推荐 SCOPE_IDENTITY; support.microsoft.com/kb/2019779。在所有情况下,你们都应该使用 OUTPUT 子句。
  • @MichaelJ.Gray 谢谢 - 我已经解决了。以前没听说过这个问题。你知道它是什么时候被发现的吗?
  • @DavidHall 在 SQL Server 2005 的发布中,他们引入了OUTPUT。我会撤回我的反对票并提供赞成票:)
【解决方案2】:

最安全的方法可能是使用 Output 子句,因为在某些情况下(多/并行处理)中 scope_idendity 存在一个已知错误。

CREATE PROCEDURE [dbo].[usp_NewTicketNumber]
AS
BEGIN
  DECLARE @NewID INT

  BEGIN TRANSACTION
  BEGIN TRY
    declare @ttIdTable TABLE (ID INT)
    INSERT INTO 
      [dbo].[TicketNumber]([CreatedDateTime], [CreatedBy])
    output inserted.id into @ttIdTable(ID)
    VALUES
      (GETDATE(), SUSER_SNAME())

    SET @NewID = (SELECT id FROM @ttIdTable)

    COMMIT TRANSACTION
  END TRY
  BEGIN CATCH
    ROLLBACK TRANSACTION
    SET @NewID = -1
  END CATCH

  RETURN @NewID
END

这样你应该是线程安全的,因为输出子句使用插入实际插入的数据,并且跨范围或会话不会有问题。

【讨论】:

【解决方案3】:
CREATE PROCEDURE [dbo].[usp_NewTicketNumber]
    @NewID int OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRY
        BEGIN TRANSACTION
        INSERT INTO 
            [dbo].[TicketNumber] ([CreatedDateTime], [CreatedBy])
        VALUES
            (GETDATE(), SUSER_SNAME())

        SET @NewID = SCOPE_IDENTITY()

        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF XACT_STATE() <> 0
            ROLLBACK TRANSACTION;
        SET @NewID = NULL;
    END CATCH
END

我不会将 RETURN 用于有意义的使用数据:无论是记录集还是输出参数。 RETURN 通常用于错误状态(就像系统存储过程在大多数情况下所做的那样):

EXEC @rtn = EXEC dbo.uspFoo
IF @rtn <> 0
    --do error stuff

您也可以使用 OUTPUT 子句来返回记录集。

这是“线程安全的”,即它可以并发运行。

【讨论】:

    【解决方案4】:

    首先 - 你为什么不一直返回新的票号而不是 0?有什么特别的原因吗?

    其次,可以肯定的是,您应该将 INSERT 和 SELECT 语句包装到 TRANSACTION 中,这样外部的任何东西都无法干预。

    第三,在 SQL Server 2005 及更高版本中,我会将我的语句包装到 TRY....CATCH 块中,如果失败则回滚事务。

    接下来,我会尽量避免在我的程序中指定数据库服务器 (TestDB42) - 如果您想将该 proc 部署到新服务器 (TestDB43) 怎么办??

    最后,我永远不会在存储过程中使用 SET NOCOUNT - 它可能导致调用者错误地认为存储过程失败(请参阅下面我对 gbn 的评论 - 如果您使用 ADO,这是一个潜在问题.NET SqlDataAdapter 对象;有关如何使用 SqlDataAdapter 修改 ADO.NET 数据的更多说明,请参阅MSDN docs

    所以我对你的存储过程的建议是:

    CREATE PROCEDURE [dbo].[usp_NewTicketNumber]
    AS
    BEGIN
      DECLARE @NewID INT
    
      BEGIN TRANSACTION
      BEGIN TRY
        INSERT INTO 
          [dbo].[TicketNumber]([CreatedDateTime], [CreatedBy])
        VALUES
          (GETDATE(), SUSER_SNAME())
    
        SET @NewID = SCOPE_IDENTITY()
    
        COMMIT TRANSACTION
      END TRY
      BEGIN CATCH
        ROLLBACK TRANSACTION
        SET @NewID = -1
      END CATCH
    
      RETURN @NewID
    END
    

    马克

    【讨论】:

    • 你永远不会使用 SET NOCOUNT ON?真的吗?
    • 好吧,如果您使用 ADO.NET 的 DataAdapter,使用 SET NOCOUNT ON 会导致 DataAdapter 错误地认为存储过程失败,因为它将返回值解释为“受影响的行数”计数 - 并且如果为 0(在 SET NOCOUNT ON 的情况下),则 DataAdapter 将其解释为“存储过程失败”。
    • 这篇文章已有 8 年历史,所以可能适用于 v1.0 点网。我已经多年没有看到这个错误了:我认为它现在已经修复了......
    • gbn:这种行为似乎仍然适用 - 这来自 .NET 3.5 文档:msdn.microsoft.com/en-us/library/59x02y99.aspx
    • Microsoft 明确表示不要在存储过程中使用 SET NOCOUNT ON,如果您将它们与 ADO.NET SqlDataAdapters 一起使用。
    【解决方案5】:

    我同意 David Hall 的回答,我只是想详细说明一下为什么在这种情况下使用 ident_current 绝对是错误的。

    我们这里有一个使用它的开发人员。来自客户端应用程序的插入发生在数据库通过自动导入导入数百万条记录的同时。返回给他的 id 来自我的流程导入的记录之一。他使用此 id 为现在附加到错误记录的一些子表创建记录。更糟糕的是,我们现在不知道在有人找不到应该在子表中的信息之前这种情况发生了多少次(他的更改已经持续了几个月)。我的自动导入不仅会干扰他的代码,而且在 smae 时间插入记录的另一个用户也可能会做同样的事情。 Ident_current 不应该用于返回刚刚插入的记录的标识,因为它不限于调用它的进程。

    【讨论】:

    • 这正是我试图避免的类型,非常感谢您添加它。
    猜你喜欢
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多