2009-03-04 42 views
24

Tengo una aplicación que potencialmente hace miles de inserciones en una base de datos de SQL Server 2005. Si una inserción falla por alguna razón (restricción de clave externa, longitud de campo, etc.) la aplicación está diseñada para registrar el error de inserción y continuar.SqlTransaction ha completado

Cada inserción es independiente de las demás, por lo que las transacciones no son necesarias para la integridad de la base de datos. Sin embargo, deseamos usarlos para la ganancia de rendimiento. Cuando uso las transacciones obtendremos el siguiente error en aproximadamente 1 de cada 100 confirmaciones.

This SqlTransaction has completed; it is no longer usable. 
    at System.Data.SqlClient.SqlTransaction.ZombieCheck() 
    at System.Data.SqlClient.SqlTransaction.Commit() 

Para tratar de localizar la causa puse rastrear declaraciones en cada operación de transacción para que pudiera garantizar la operación no estaba cerrada antes de llamar a comprometerse. Confirmé que mi aplicación no estaba cerrando la transacción. Luego volví a ejecutar la aplicación usando exactamente los mismos datos de entrada y tiene éxito.

Si apago el registro, falla nuevamente. Vuelve a encenderlo y tiene éxito. Este botón de encendido/apagado se realiza a través de app.config sin la necesidad de recompilar.

Obviamente, el acto de registrar cambia el tiempo y hace que funcione. Esto indicaría un problema de enhebrado. Sin embargo, mi aplicación no tiene múltiples subprocesos.

He visto una entrada de MS KB que indica un error con .Net 2.0 framework podría causar problemas similares (http://support.microsoft.com/kb/912732). Sin embargo, la solución que proporcionaron no resuelve este problema.

+2

¿Usar transacciones "para la ganancia de rendimiento"? ¿De qué manera el uso de transacciones mejora el rendimiento? – LukeH

+0

De acuerdo con luke, ¿por qué ves o crees que tienes un aumento de rendimiento al hacerlo? – eglasius

+8

tener una transacción en torno a varias inserciones, en general, aumentar el rendimiento, probarlo usted mismo y ver. al realizar una transacción, reduce el número de bloqueos que debe adquirir antes de las inserciones –

Respuesta

7

Difícil de ver sin ver el código. Supongo que de su descripción está utilizando una transacción para comprometerse después de cada N insertos, lo que mejorará el rendimiento frente a la confirmación de cada inserción, siempre que N no sea demasiado grande.

Pero la desventaja es: si falla una inserción, cualquier otra inserción dentro del lote actual de N se retrotraerá cuando restituya la transacción.

En general, debe deshacerse de una transacción antes de cerrar la conexión (lo que revertirá la transacción si no se ha confirmado). El patrón habitual tiene un aspecto similar al siguiente:

using(SqlConnection connection = ...) 
{ 
    connection.Open(); 
    using(SqlTransaction transaction = connection.BeginTransaction()) 
    { 
     ... do stuff ... 
     transaction.Commit(); // commit if all is successful 
    } // transaction.Dispose will be called here and will rollback if not committed 
} // connection.Dispose called here 

Por favor, escriba el código si necesita más ayuda.

+0

Normalmente uso este modelo. Sin embargo, en este caso, específicamente, no quiero que se deshagan las otras operaciones. Simplemente quiero registrar el problema y reanudarlo. La transacción solo se usa como un impulso en el rendimiento. – aef123

6

Tenga en cuenta que su aplicación no es el único participante en la transacción; el SQL Server también está involucrado.

El error que cito:

Este SqlTransaction ha completado; ya no se puede usar. en System.Data.SqlClient.SqlTransaction.ZombieCheck() en System.Data.SqlClient.SqlTransaction.Commit()

no indica que la transacción haya comitted, sólo que es completa.

Mi primera sugerencia es que su servidor ha cancelado la transacción porque tardó demasiado (tiempo de pared reducido) o se hizo demasiado grande (demasiados cambios o demasiados bloqueos).

Mi segunda sugerencia es comprobar que está limpiando las conexiones y las transacciones de forma adecuada.Es posible que tenga problemas porque ocasionalmente está agotando un grupo de recursos antes de que las cosas se reciclen automáticamente.

Por ejemplo, DbConnection implementa IDisposable, por lo que necesita para asegurarse de que limpiar apropiadamente - con una declaración using si es posible, o llamando Dispose() directamente si usted no puede. 'DbCommand' es similar, ya que también implementa IDisposable.

1

Bueno, primer IMO, no debe esperar que su aplicación se ocupe de errores "duros" como estos. Su aplicación debe comprender cuáles son estas reglas comerciales y dar cuenta de ellas. NO fuerce su base de datos para que sea la regla de negocio o la regla de restricción. Solo debe ser un policía de reglas de datos, y en eso, tenga cuidado al decirle a la interfaz con RETURN y otros métodos. Las cosas de Schema no deberían forzarse tan lejos.

Para responder a su pregunta, sospecho, sin ver su código, que está intentando realizar una confirmación cuando se ha producido un error y aún no lo sabe. Este es el razonamiento detrás de mi primera declaración ... tratando de atrapar el error que proporciona la base de datos, sin que su aplicación comprenda y participe en estas reglas, se está dando un dolor de cabeza.

Pruebe esto. Insertar filas que no fallará con una restricción de base de datos u otros errores y procesará las inserciones con una confirmación. Mira qué pasa. Luego inserte en algunos registros que fallarán, procesen una confirmación y verifique si obtiene su hermoso error. En tercer lugar, vuelva a ejecutar los errores, realice una reversión forzada y vea si tiene éxito.

Solo algunas ideas. Una vez más, como resumen, creo que tiene que ver con no atrapar ciertos errores de la base de datos de una manera elegante para cosas que son errores "duros" y esperando que la interfaz los enfrente. De nuevo, mi opinión experta, NOPE, no lo hagas. Es un desastre. Su aplicación debe superponerse en el conocimiento sobre las reglas en la parte posterior. Esas cosas están en su lugar solo para asegurarse de que esto no ocurra durante las pruebas, y el error ocasional que aparece así, para luego colocarlo en el frente para manejar la búsqueda olvidada de un conjunto de tablas de claves externas.

Espero que ayude.

+0

Normalmente, estoy de acuerdo con usted acerca de la aplicación que trata las reglas comerciales. Sin embargo, en este caso, la aplicación es una herramienta de conversión de datos. El usuario final configura todas las reglas comerciales. Es responsabilidad del usuario crear su conversión de forma que acepte el DB. Es por eso que necesito iniciar sesión y continuar – aef123

+0

de acuerdo también. Mi respuesta es apoyar por qué está ocurriendo el problema. La aplicación no ha contabilizado un error de falla IMO. Ejecute el proceso almacenado como una declaración en el Administrador corporativo de SQL (sin pasar por la aplicación) y vea cuáles son los resultados, exitosos o no. – SnapJag

0

Ha demostrado que sus datos están bien, más allá de toda duda razonable.
FWIW, Preferiría mover el inserto a un SPROC y no usar la transacción en absoluto.
Si necesita que la IU responda, use un trabajador en segundo plano para hacer el ruido de la base de datos.
Para mí, una transacción es para actividades interrelacionadas, no como un dispositivo de ahorro de tiempo. El costo de inserción tiene que ser pagado en algún lugar a lo largo de la línea.

Recientemente utilicé ANTS profiler en una aplicación de base de datos y me asombró ver que las intermitentes excepciones de SQlClient se mostraban en el código de ejecución sólida. Los errores son profundos en el marco al abrir una conexión. Nunca llegan a la superficie y no son detectables por el código del cliente. Entonces ... ¿El punto?
No todo es sólido, mueve el trabajo pesado del U.I. y acepta el costo HTH Bob

7

Gracias por todo el comentario. He estado trabajando con alguien de MSFT en los foros de MSDN para descubrir qué está pasando. Resulta que el problema se debe a que una de las inserciones falla debido a un problema de conversión de fecha y hora.

El principal problema es el hecho de que este error aparece si se trata de un error de conversión de fecha. Sin embargo, si se trata de otro error, como que un campo sea demasiado largo, no causa este problema. En ambos casos, esperaría que la transacción aún existiera, así que puedo llamar Rollback en ella.

Tengo un programa de ejemplo completo para replicar este problema. Si alguien desea verlo o el intercambio con MSFT, puede encontrar el hilo en los grupos de noticias de MSFT en microsoft.public.dotnet.framework.adonet bajo el hilo de error SqlTransaction.ZombieCheck.

+3

El resultado final fue que SQL Server revierte automáticamente las transacciones en lo que considera errores graves. El error de conversión de DateTime se considera uno de estos errores graves. Actualmente no hay forma de evitar que SQL Server termine su transacción por este tipo de error. – aef123

3

Esta excepción se produce porque la transacción DB real ya se ha retrotraído, por lo que un objeto .NET que lo representa en el lado del cliente ya es un "zombie".

Explicación más detallada es here. This post explica cómo escribir un código de reversión de transacción correcto para tales escenarios.

1

Mi problema fue similar, y resultó que me lo estaba haciendo a mí mismo. Estaba ejecutando un conjunto de scripts en un proyecto VS2008 para crear procedimientos almacenados. En uno de los procs, utilicé una transacción. El script estaba ejecutando la creación del proceso dentro de una transacción, en lugar de incluir el código de transacción como parte del procedimiento. Entonces, cuando llegó al final sin un error, cometió la transacción para el script. El código .Net también estaba usando y luego cometiendo una transacción, y el efecto zombie vino cuando intentó cerrar la transacción que ya se había cerrado dentro del script SQL. Eliminé la transacción del script SQL, confiando en la transacción abierta y comprobada en el código .Net, y esto resolvió el problema.

1

De acuerdo con este mensaje: http://blogs.msdn.com/b/dataaccesstechnologies/archive/2010/08/24/zombie-check-on-transaction-error-this-sqltransaction-has-completed-it-is-no-longer-usable.aspx

Una solución temporal podría ser intentar agarrar la reversión o la operación de confirmación. Por lo que este código será suficiente para detener el error de estar lanzando:

public static void TryRollback(this System.Data.IDbTransaction t) 
    { 
     try 
     { 
      t.Rollback(); 
     } 
     catch (Exception ex) 
     { 
      // log error in my case 
     } 
    } 

    public static void TryCommit(this System.Data.IDbTransaction t) 
    { 
     try 
     { 
      t.Commit(); 
     } 
     catch (Exception ex) 
     { 
      // log error in my case 
     } 
    } 

Comprobar este ejemplo del sitio Web MSDN: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction.aspx

0

también se enfrentaron al mismo problema.

This SqlTransaction has completed; it is no longer usable 

Después de investigar un poco, descubrí que el tamaño del archivo de registro de mi base de datos es de 40 GB redondos.
Es un gran volumen de datos que causa que mi transacción sql no se haya comprometido.

Por lo tanto, mi solución es shrink tamaño del archivo de registro al ver this reference link.

CREATE PROCEDURE sp_ShrinkLog_ByDataBaseName(
@DataBaseName VARCHAR(200) 
) 
AS 
BEGIN 

DECLARE @DBName VARCHAR(200) 
DECLARE @LogFileName VARCHAR(200) 
SET @DBName = @DataBaseName 

SELECT @LogFileName = [Logical File Name] 
FROM 
(
SELECT 
    DB_NAME(database_id) AS "Database Name", 
type_desc    AS "File Type", 
name     AS "Logical File Name", 
physical_name   AS "Physical File", 
state_desc   AS "State" 
FROM 
    SYS.MASTER_FILES 
WHERE 
    database_id = DB_ID(@DBName) 
    and type_desc = 'LOG' 
) AS SystemInfo 
SELECT LogFileName = @LogFileName 

EXECUTE(
'USE ' + @DBName + '; 

-- Truncate the log by changing the database recovery model to SIMPLE. 
ALTER DATABASE ' + @DBName + ' 
SET RECOVERY SIMPLE; 

-- Shrink the truncated log file to 1 MB. 
DBCC SHRINKFILE (' + @LogFileName + ', 1); 

-- Reset the database recovery model. 
ALTER DATABASE ' + @DBName + ' 
SET RECOVERY FULL; 

') 
END 

Luego ejecútelo EXEC sp_ShrinkLog_ByDataBaseName 'Yor_DB_NAME'.

Si esto no funciona para usted, aquí está another solution, que creo que funcionará.

Cuestiones relacionadas