2008-09-29 7 views
7

Recientemente me han encargado la depuración de un problema extraño dentro de una aplicación de comercio electrónico. Después de una actualización de la aplicación, el sitio comenzó a colgar de vez en cuando y me enviaron a depuración. Después de verificar el registro de eventos, encontré que el servidor SQL escribió ~ 200 000 eventos en un par de minutos con el mensaje que decía que una restricción había fallado. Después de mucha depuración y algunos rastreos encontré al culpable. He quitado algo de código innecesario y limpié un poco, pero básicamente esto es élWhile-cláusula en T-SQL que se repite siempre

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 

Datos:

  1. sólo hay uno o dos registros que coincide con el de primera cláusula select
  2. RowCount de la instrucción DELETE indica que se ha eliminado
  3. el bucle WHILE cláusula siempre

El procedimiento se ha reescrito para seleccionar las filas que se deben eliminar en una tabla temporal en memoria para que se solucione el problema inmediato, pero esto realmente despertó mi curiosidad.

¿Por qué gira para siempre?

Aclaración: El borrado no falla (@@ recuento de filas es 1 después de la stmt borrado cuando depurando) Aclaración 2: No debería importar si es o no la parte superior SELECT ... cláusula se ordena por cualquier campo específico ya que el registro con la identificación devuelta se eliminará, por lo que en el siguiente ciclo debería obtener otro registro.

Actualización: Después de verificar los registros de subversión, encontré la confirmación de culpa que hizo que este procedimiento almacenado se volviera loco. La única diferencia real que puedo encontrar es que anteriormente no había unión en la instrucción SELECT TOP 1, es decir, sin esa unión funcionó sin ninguna declaración de transacción que rodeara la eliminación. Parece ser la introducción de la combinación que hizo que el servidor SQL sea más exigente.

actualización aclaración: brien señaló que no hay necesidad de la unión, sino que se hace realmente utilizar algunos campos de la tabla GoodsForSale pero yo les he quitado para mantener el código simplemente para que podamos concentrarnos en el problema mano

+0

¿Qué restricción falló? ¿Estaba en ShoppingCartItem o GoodsForSale? – brien

+0

Vea mi respuesta, ¿esta pregunta aún está abierta? –

Respuesta

3

¿Está operando en forma explícita o implícita transaction mode?

Dado que está en modo explícito, creo que debe rodear la operación DELETE con las instrucciones BEGIN TRANSACTION y COMMIT TRANSACTION.

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 

Aclaración: La razón por la que había necesidad de utilizar las transacciones es que la eliminación no sucede realmente en la base de datos hasta que lo haga una operación de confirmación. Esto generalmente se usa cuando tiene múltiples operaciones de escritura en una transacción atómica. Básicamente, solo desea que los cambios sucedan en el DB si todas las operaciones son exitosas.

En su caso, solo hay 1 operación, pero como está en modo de transacción explícita, necesita decirle a SQL Server que realmente realice los cambios.

+0

explícito, por favor elabore –

+0

que suena plausible, ¿podría explicar por qué debería rodearlo con declaraciones de transacciones? –

+0

por favor, se mi actualización sobre la introducción de la unión –

0

Obviamente, algo no está siendo eliminado o modificado donde debería. Si la condición sigue siendo la misma en la siguiente iteración, continuará.

Además, está usted comparando @TmpShoppingCartItemID, y no @PurchID. Puedo ver cómo podrían ser diferentes, y podría eliminar una fila diferente de la que se está verificando en la instrucción while.

-1

No estoy seguro si entiendo el problema, pero en la cláusula de selección está haciendo una unión interna con otra tabla. Esa unión puede causar que no se obtengan registros y luego la eliminación falla. Intenta usar una combinación izquierda.

+0

Dijo que el recuento de filas en la eliminación es 1, por lo que está eliminando el elemento. – brien

1

¿Hay un registro en ShoppingCartItem con esa @PurchID donde el GFSID no está en la tabla GoodsForSale? Eso explicaría por qué EXISTS devuelve verdadero, pero no hay más registros para eliminar.

+0

Eso no es así porque la eliminación no falla, @@ rowcount es 1 después de la eliminación. –

+0

Aclaró que la eliminación fue exitosa, así que no creo que eso lo explique. – brien

0

Si los comentarios anteriores no te ayudan en lo que va, propongo la adición/sustitución:

DECLARE [email protected] INT 

SET @OldShoppingCartItemID = 0 

WHILE EXISTS (SELECT ... WHERE ShoppingCartItemID > @ShoppingCartItemID) 

SELECT TOP 1 WHERE ShoppingCartItemID > @OldShoppingCartItemID ORDER BY ShoppingCartItemID 

SET @OldShoppingCartItemID = @TmpShoppingCartItemID 
3
FROM 
    ShoppingCartItem 
    INNER JOIN 
    GoodsForSale 
    on ShoppingCartItem.GFSID = GoodsForSale.GFSID 

Vaya, se unen a su trae el conjunto de resultados a cero filas.

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

Oops, ha utilizado asignación múltiple en un conjunto sin filas. Esto hace que las variables permanezcan sin cambios (tendrán el mismo valor que tuvieron la última vez a través del ciclo). Las variables NO se asignan a nulo en este caso.

Si coloca este código en el inicio del bucle, será (correctamente) fallan más rápido:

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

Si cambia su código en busca de una llave (sin unión) y luego ir a buscar los datos relacionados por clave en una segunda consulta, ganarás.

0

Si hay artículos de carrito de compras que no existen en la tabla GoodsForSale, esto se convertirá en un bucle infinito.

trate de cambiar su declaración existe de tener en cuenta que

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

O mejor aún, volver a escribir esto por lo que no requiere un bucle. Bucle como este es un bucle infinito esperando a suceder. Debe reemplazar con operaciones basadas en conjunto y una transacción.

Cuestiones relacionadas