2009-03-04 23 views
24

Possiedo un'applicazione che potenzialmente esegue migliaia di inserimenti in un database di SQL Server 2005. Se un inserto non riesce per qualsiasi motivo (vincolo di chiave esterna, lunghezza del campo, ecc.) L'applicazione è progettata per registrare l'errore di inserimento e continuare.SqlTransaction ha completato

Ogni inserto è indipendente dagli altri, pertanto le transazioni non sono necessarie per l'integrità del database. Tuttavia, desideriamo usarli per il guadagno in termini di prestazioni. Quando uso le transazioni otterremo il seguente errore su circa 1 su 100 commit.

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

per cercare di rintracciare la causa ho messo tracciare dichiarazioni ad ogni operazione di transazione in modo da poter garantire la transazione non era stato chiuso prima di chiamare il commit. Ho confermato che la mia app non ha chiuso la transazione. Ho quindi eseguito nuovamente l'app utilizzando gli stessi dati di input e ci riesce.

Se si disattiva la registrazione, fallisce nuovamente. Riaccendilo e ci riesce. Questo interruttore on/off viene eseguito tramite app.config senza la necessità di ricompilare.

Ovviamente l'atto di registrazione cambia i tempi e fa sì che funzioni. Ciò indicherebbe un problema di threading. Tuttavia, la mia app non è multi-thread.

Ho visto una voce MS KB che indica un bug con .Net 2.0 framework potrebbe causare problemi simili (http://support.microsoft.com/kb/912732). Tuttavia, la correzione fornita non risolve questo problema.

+2

Utilizzare le transazioni "per il guadagno di prestazioni"? In che modo l'utilizzo delle transazioni migliora le prestazioni? – LukeH

+0

Concordato con luke, perché vedi o pensi di avere un guadagno in termini di prestazioni? – eglasius

+8

avere una transazione intorno a più inserti, in generale, aumenterà le prestazioni, testarlo tu stesso e vedere. effettuando una transazione si riduce il numero di blocchi da acquisire prima degli inserimenti –

risposta

7

Difficile aiutare senza vedere il codice. Presumo dalla tua descrizione che stai utilizzando una transazione da impegnare dopo ogni N inserti, che migliorerà le prestazioni rispetto al commit di ogni inserto a condizione che N non sia troppo grande.

Ma il lato negativo è: se un inserto fallisce, tutti gli altri inserimenti all'interno del batch corrente di N verranno ripristinati al rollback della transazione.

In generale, è necessario disporre una transazione prima di chiudere la connessione (operazione che arresta la transazione se non è stata eseguita la commit). Il solito schema assomiglia al seguente:

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 

Si prega di inviare il codice se avete bisogno di più aiuto.

+0

Normalmente uso questo modello. Tuttavia, in questo caso, in particolare, non voglio che le altre operazioni vengano ripristinate. Voglio solo registrare il problema e riprendere. La transazione viene utilizzata solo come incremento delle prestazioni. – aef123

6

Tenere presente che l'applicazione non è l'unico partecipante alla transazione, anche SQL Server è coinvolto.

L'errore che cito:

Questo SqlTransaction ha completato; it non è più utilizzabile. a System.Data.SqlClient.SqlTransaction.ZombieCheck() a System.Data.SqlClient.SqlTransaction.Commit()

non indica che la transazione è ha commesso, solo che è completa.

Il mio primo suggerimento è che il server ha eliminato la transazione perché ha impiegato troppo tempo (tempo di parete ellissato) o troppo grande (troppe modifiche o troppi blocchi).

Il mio secondo suggerimento è di verificare che si stiano pulendo le connessioni e le transazioni in modo appropriato.È possibile che tu stia incontrando dei problemi perché di tanto in tanto stai esaurendo una serie di risorse prima che le cose vengano automaticamente riciclate.

Per esempio, DbConnection implementa IDisposable, quindi è necessario assicurarsi di pulire in modo appropriato - con una dichiarazione using se potete, o chiamando direttamente Dispose() se non è possibile. 'DbCommand' è simile, poiché implementa anche IDisposable.

1

Bene, primo IMO, non dovresti aspettarti che la tua app abbia a che fare con errori "duri" come questi. La tua applicazione dovrebbe capire quali sono queste regole aziendali e renderle conto. NON forzare il database a essere la regola aziendale o il sistema di regole di restrizione. Dovrebbe essere solo un poliziotto per la regola dei dati e, a tal fine, essere gentile nel dire all'interfaccia con RETURN e altri metodi. Le cose dello schema non dovrebbero essere forzate così lontano.

On per rispondere alla tua domanda, sospetto, senza vedere alcun codice, che stai tentando di eseguire un commit quando si è verificato un errore e non lo sai ancora. Questo è il ragionamento alla base della mia prima affermazione ... cercando di intercettare l'errore fornito dal database, senza che la tua applicazione comprenda e partecipi a queste regole, ti stai dando un mal di testa.

Prova questo. Inserire le righe che non risolveranno con un vincolo di database o altri errori ed elaborare gli inserimenti con un commit. Guarda cosa succede. Quindi, in alcuni record, inserire in, elaborare un commit e vedere se si ottiene il tuo errore. In terzo luogo, esegui nuovamente gli errori, esegui un rollback forzato e verifica se riesci.

Solo alcune idee. Ancora una volta, come riassunto, penso che abbia a che fare con il non intrappolare determinati errori dal database in modo aggraziato per cose che sono errori "difficili" e che si aspettano che il front-end si occupi di loro. Di nuovo, la mia opinione di esperti, NOPE, non lo fa. È un disastro. La tua app deve sovrapporsi alla conoscenza delle regole sul retro. Queste cose sono in atto solo per assicurarsi che questo non accada durante i test, e il bug occasionale che affiora in questo modo, per poi metterlo in primo piano per gestire la ricerca dimenticata su un set di tabelle di chiavi straniere.

Spero che aiuti.

+0

Normalmente sono d'accordo con te sull'app che si occupa delle regole aziendali. Tuttavia, in questo caso l'app è uno strumento di conversione dei dati. L'utente finale imposta tutte le regole aziendali. È responsabilità degli utenti creare la conversione in un modo accettato dal DB. Questo è il motivo per cui ho bisogno di accedere e continuare – aef123

+0

concordato pure. La mia risposta è di sostenere perché il problema si sta verificando. L'applicazione non ha contabilizzato un errore non riuscito IMO. Esegui il proc memorizzato come una dichiarazione in SQL Enterprise Manager (ignorando l'app) e vedi quali sono i risultati, con successo o meno. – SnapJag

0

Hai dimostrato che i tuoi dati vanno bene oltre ogni ragionevole dubbio.
FWIW, Preferirei spostare l'inserto in un SPROC e non utilizzare affatto la transazione.
Se è necessario che l'interfaccia utente sia reattiva, utilizzare un worker in background per eseguire il grunt del database.
Per me, una transazione è per attività correlate, non per risparmiare tempo. Il costo di inserimento deve essere pagato da qualche parte lungo la linea.

Recentemente ho utilizzato il profiler ANTS su un'applicazione di database ed è stato stupito nel vedere le eccezioni SQlClient intermittenti che mostrano in codice con prestazioni solide. Gli errori sono profondi nel framework quando si apre una connessione. Non arrivano mai in superficie e non sono rilevabili dal codice client. Quindi ... Il punto?
Non è tutto rock solido là fuori, spostare il lavoro pesante fuori dalla U.I. e accetta il costo. HTH Bob

7

Grazie per tutto il feedback. Ho lavorato con qualcuno di MSFT sui forum MSDN per capire cosa sta succedendo. Si scopre che il problema è dovuto a una delle mancate inserzioni a causa di un problema di conversione di data e ora.

Il problema principale è il fatto che questo errore si presenta se si tratta di un errore di conversione della data. Tuttavia, se si tratta di un altro errore, ad esempio un campo troppo lungo, non causa questo problema. In entrambi i casi, mi aspetto che la transazione continui a esistere, quindi posso richiamare il rollback.

Ho un programma di esempio completo per replicare questo problema. Se qualcuno desidera vederlo o lo scambio con MSFT è possibile trovare il thread sui newsgroup di MSFT in microsoft.public.dotnet.framework.adonet sotto il thread di errore SqlTransaction.ZombieCheck.

+3

Il risultato finale è che SQL Server esegue automaticamente il rollback delle transazioni su ciò che considera errori gravi. L'errore di conversione DateTime è considerato uno di questi errori gravi. Attualmente non esiste alcun modo per impedire a SQL Server di terminare la transazione per questo tipo di errore. – aef123

3

Questa eccezione viene generata perché la transazione DB effettiva è già stata ripristinata, quindi un oggetto .NET che lo rappresenta sul lato client è già uno "zombie".

Una spiegazione più dettagliata è here. This post spiega come scrivere un codice di rollback della transazione corretto per tali scenari.

1

Il mio problema era simile, e ho scoperto che lo stavo facendo da solo. Stavo eseguendo una serie di script in un progetto VS2008 per creare stored procedure. In uno dei procs, ho usato una transazione. Lo script stava eseguendo la creazione del proc all'interno di una transazione, piuttosto che includere il codice transazione come parte della procedura. Quindi, quando è arrivato alla fine senza errori, ha commesso la transazione per lo script. Il codice .Net stava anche usando e poi commettendo una transazione, e l'effetto zombi arrivò quando provò a chiudere la transazione che era già stata chiusa all'interno dello script SQL. Ho rimosso la transazione dallo script SQL, facendo affidamento sulla transazione aperta e controllato nel codice .Net, e questo ha risolto il problema.

1

Secondo questo post: 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 soluzione temporanea potrebbe essere quello di cercare di prendere il rollback o commettere il funzionamento. Quindi questo codice sarà sufficiente per fermare il bug da buttare:

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 
     } 
    } 

controllare questo esempio dal sito Web MSDN: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction.aspx

0

Ho anche affrontato lo stesso problema.

This SqlTransaction has completed; it is no longer usable 

Dopo aver effettuato alcune ricerche, ho scoperto che la dimensione del file di registro del datatabase è di 40 GB.
È un volume di dati di grandi dimensioni che causa la mia transazione sql non impegnata.

Quindi, la mia soluzione è la dimensione del file di registro shrink vedendo 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 

Quindi eseguirlo EXEC sp_ShrinkLog_ByDataBaseName 'Yor_DB_NAME'.

Se questo non funziona per voi, ecco another solution che penso che funzionerà.

Problemi correlati