【问题标题】:While-clause in T-SQL that loops foreverT-SQL 中的 while 子句永远循环
【发布时间】:2010-09-14 02:18:03
【问题描述】:

我最近的任务是调试电子商务应用程序中的一个奇怪问题。应用程序升级后,网站开始不时挂起,我被派去调试。检查事件日志后,我发现 SQL 服务器在几分钟内写入了大约 200 000 个事件,并显示约束失败的消息。经过多次调试和一些跟踪,我找到了罪魁祸首。我已经删除了一些不必要的代码并对其进行了一些清理,但基本上就是这样

WHILE EXISTS (SELECT * FROM ShoppingCartItem WHERE ShoppingCartItem.PurchID = @PurchID)
BEGIN
    SELECT TOP 1 
        @TmpGFSID = ShoppingCartItem.GFSID, 
        @TmpQuantity = ShoppingCartItem.Quantity,
        @TmpShoppingCartItemID = ShoppingCartItem.ShoppingCartItemID,
    FROM
        ShoppingCartItem INNER JOIN GoodsForSale on ShoppingCartItem.GFSID = GoodsForSale.GFSID
    WHERE ShoppingCartItem.PurchID = @PurchID

    EXEC @ErrorCode = spGoodsForSale_ReverseReservations @TmpGFSID, @TmpQuantity
    IF @ErrorCode <> 0
    BEGIN
        Goto Cleanup    
    END

    DELETE FROM ShoppingCartItem WHERE ShoppingCartItem.ShoppingCartItemID = @TmpShoppingCartItemID
    -- @@ROWCOUNT is 1 after this
END

事实:

  1. 只有一个或两个记录匹配第一个选择子句
  2. DELETE 语句中的 RowCount 表明它已被删除
  3. WHILE 子句将永远循环

该过程已被重写,以选择应删除的行到临时内存表中,从而解决了眼前的问题,但这确实激发了我的好奇心。

为什么它会永远循环?

澄清:delete不会失败(调试时delete stmt后@@rowcount为1) 澄清 2:SELECT TOP ... 子句是否按任何特定字段排序都无关紧要,因为具有返回 id 的记录将被删除,因此在下一个循环中它应该得到另一个记录。

更新:检查颠覆日志后,我发现了导致此存储过程失控的罪魁祸首提交。我能找到的唯一真正的区别是,之前在 SELECT TOP 1 语句中没有加入,即没有加入,它在没有任何围绕删除的事务语句的情况下工作。似乎是连接的引入使 SQL Server 更加挑剔。

更新说明brien 指出不需要连接,但我们确实使用了 GoodsForSale 表中的一些字段,但我删除了它们以保持代码简单,以便我们可以专注于手头的问题

【问题讨论】:

  • 什么约束失败了?是在 ShoppingCartItem 还是 GoodsForSale 上?
  • 看看我的回答,这个问题还没有解决吗?

标签: sql sql-server tsql


【解决方案1】:

你是在显式还是隐式transaction mode操作?

由于您处于显式模式,我认为您需要用 BEGIN TRANSACTION 和 COMMIT TRANSACTION 语句包围 DELETE 操作。

WHILE EXISTS (SELECT * FROM ShoppingCartItem WHERE ShoppingCartItem.PurchID = @PurchID)
BEGIN
    SELECT TOP 1 
            @TmpGFSID = ShoppingCartItem.GFSID, 
            @TmpQuantity = ShoppingCartItem.Quantity,
            @TmpShoppingCartItemID = ShoppingCartItem.ShoppingCartItemID,
    FROM
            ShoppingCartItem INNER JOIN GoodsForSale on ShoppingCartItem.GFSID = GoodsForSale.GFSID
    WHERE ShoppingCartItem.PurchID = @PurchID

    EXEC @ErrorCode = spGoodsForSale_ReverseReservations @TmpGFSID, @TmpQuantity
    IF @ErrorCode <> 0
    BEGIN
            Goto Cleanup    
    END

    BEGIN TRANSACTION delete

        DELETE FROM ShoppingCartItem WHERE ShoppingCartItem.ShoppingCartItemID = @TmpShoppingCartItemID
        -- @@ROWCOUNT is 1 after this

    COMMIT TRANSACTION delete
END

澄清:您需要使用事务的原因是,在您执行 COMMIT 操作之前,删除实际上不会在数据库中发生。这通常在原子事务中有多个写入操作时使用。基本上,您只希望在所有操作都成功的情况下对数据库进行更改。

在您的情况下,只有 1 个操作,但由于您处于显式事务模式,您需要告诉 SQL Server真正进行更改。

【讨论】:

  • 这听起来很合理,请您详细说明为什么我应该用交易语句包围它。
  • 我正在尝试用 update 语句做类似的事情,但即使我在脚本中包含事务,它仍然无法工作并进入永久循环。我的脚本是:声明 AtPart varchar(20) (我放置 At 而不是猴子符号,只是因为编写 cmets 的规则才放在这里) while exists ((select top 1 * from partiidev p where isnull(brojRacuna,'')='' ) begin set AtPart=(select top 1 partija from partiidev p where isnull(brojRacuna,'')='') begin transaction t1 update partiidev set BrojRacuna= (select dbo.dev_brojracuna (AtPart)) where partija like AtPart commit transaction t1 end
【解决方案2】:
FROM
  ShoppingCartItem
    INNER JOIN
  GoodsForSale
    on ShoppingCartItem.GFSID = GoodsForSale.GFSID

糟糕,您的联接将结果集减少到零行。

 SELECT TOP 1
    @TmpGFSID = ShoppingCartItem.GFSID,
    @TmpQuantity = ShoppingCartItem.Quantity,
    @TmpShoppingCartItemID =
      ShoppingCartItem.ShoppingCartItemID

糟糕,您对一个没有行的集合使用了多重赋值。这会导致变量保持不变(它们将具有与上次循环时相同的值)。在这种情况下,变量不会被分配为 null。

如果您将此代码放在循环的开头,它将(正确地)更快地失败:

 SELECT
    @TmpGFSID = null,
    @TmpQuantity = null,
    @TmpShoppingCartItemID = null

如果您更改代码以获取密钥(不加入),然后在第二个查询中通过密钥获取相关数据,您将获胜。

【讨论】:

    【解决方案3】:

    ShoppingCartItem 中是否存在带有 @PurchID 的记录,而 GFSID 不在 GoodsForSale 表中?这可以解释为什么 EXISTS 返回 true,但没有更多记录要删除。

    【讨论】:

    • 不是这样,因为删除没有失败,删除后@@rowcount为1。
    • 他澄清说删除成功了,所以我认为这不能解释。
    【解决方案4】:

    如果 GoodsForSale 表中不存在任何购物车商品,那么这将进入无限循环。

    尝试更改您的存在语句以考虑到这一点

    (SELECT * FROM ShoppingCartItem WHERE  JOIN GoodsForSale on ShoppingCartItem.GFSID = GoodsForSale.GFSID where ShoppingCartItem.PurchID = @PurchID)
    

    或者更好的是,重写它,这样它就不需要循环了。像这样的循环是一个等待发生的无限循环。您应该替换为基于集合的操作和事务。

    【讨论】:

      【解决方案5】:

      显然,某些内容没有被删除或修改。如果在下一次迭代中条件仍然相同,它将继续进行。

      另外,您正在比较的是@TmpShoppingCartItemID,而不是@PurchID。我可以看到这些可能会有所不同,您可以删除与 while 语句中正在检查的行不同的行。

      【讨论】:

        【解决方案6】:

        如果上面的cmets到目前为止没有帮助到你,我建议添加/替换:

        DECLARE Old@ShoppingCartItemID INT
        
        SET @OldShoppingCartItemID = 0
        
        WHILE EXISTS (SELECT ... WHERE ShoppingCartItemID > @ShoppingCartItemID)
        
        SELECT TOP 1 WHERE ShoppingCartItemID > @OldShoppingCartItemID ORDER BY ShoppingCartItemID 
        
        SET @OldShoppingCartItemID = @TmpShoppingCartItemID
        

        【讨论】:

          【解决方案7】:

          我不确定我是否理解这个问题,但在 select 子句中它正在与另一个表进行内部连接。该连接可能导致没有记录,然后删除失败。尝试使用左连接。

          【讨论】:

          • 他说delete上的rowcount是1,所以是在删除item。
          猜你喜欢
          • 2014-05-30
          • 2016-03-30
          • 2021-11-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-20
          相关资源
          最近更新 更多