25 votes

SqlTransaction a terminé

J'ai une application qui effectue potentiellement des milliers d'insertions dans une base de données SQL Server 2005. Si une insertion échoue pour une raison quelconque (contrainte de clé étrangère, longueur de champ, etc.), l'application est conçue pour enregistrer l'erreur d'insertion et continuer.

Chaque insertion étant indépendante des autres, les transactions ne sont pas nécessaires pour l'intégrité de la base de données. Cependant, nous souhaitons les utiliser pour le gain de performance. Lorsque j'utilise les transactions, nous obtenons l'erreur suivante sur environ un commit sur cent.

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

Pour essayer de trouver la cause, j'ai placé des instructions de suivi à chaque opération de transaction afin de m'assurer que la transaction n'était pas fermée avant d'appeler le commit. J'ai confirmé que mon application ne fermait pas la transaction. J'ai ensuite relancé l'application en utilisant exactement les mêmes données d'entrée et elle a réussi.

Si j'éteins la connexion, l'échec persiste. Si je l'active à nouveau, il réussit. Cette activation/désactivation s'effectue via le fichier app.config sans qu'il soit nécessaire de recompiler.

Il est évident que l'acte d'enregistrement modifie le timing et le fait fonctionner. Cela indiquerait un problème de threading. Cependant, mon application n'est pas multithreadée.

J'ai vu une entrée dans la base de données MS indiquant qu'un bogue dans le cadre de .Net 2.0 pouvait causer des problèmes similaires ( http://support.microsoft.com/kb/912732 ). Cependant, la solution qu'ils ont fournie ne résout pas ce problème.

1voto

SnapJag Points 476

Tout d'abord, vous ne devriez pas vous attendre à ce que votre application traite des erreurs "difficiles" comme celles-ci. Votre application doit comprendre ce que sont ces règles de gestion et en tenir compte. N'obligez pas votre base de données à être le flic des règles de gestion ou des règles de contrainte. Elle ne devrait être que le flic de la règle de données, et à ce niveau, être gracieuse en indiquant l'interface avec les RETURNs et autres méthodes. Les schémas ne devraient pas être forcés aussi loin.

Pour répondre à votre question, je soupçonne, sans voir votre code, que vous essayez de faire un commit alors qu'une erreur s'est produite, et que vous ne le savez pas encore. C'est le raisonnement derrière ma première déclaration ... essayer de piéger l'erreur que la base de données donne, sans que votre application comprenne et participe à ces règles, vous vous donnez un mal de tête.

Essaie ça. Insérez les lignes qui ne le fera pas échouer avec une contrainte de base de données, ou d'autres erreurs et traiter les insertions avec un commit. Voir ce qui se passe. Ensuite, insérez des enregistrements qui se échouez, traitez un commit et voyez si vous obtenez votre belle erreur. Troisièmement, exécutez à nouveau les erreurs, faites un rollback forcé, et voyez si vous réussissez.

Juste quelques idées. Encore une fois, pour résumer, je pense que cela a à voir avec le fait de ne pas piéger certaines erreurs de la base de données de manière gracieuse pour les choses qui sont des erreurs "difficiles" et d'attendre du front-end qu'il s'en occupe. Encore une fois, mon avis d'expert, NOPE, ne le faites pas. C'est un désordre. Votre application a besoin de se chevaucher dans la connaissance des règles sur le dos. Ces choses sont en place juste pour s'assurer que cela ne se produit pas pendant les tests, et le bug occasionnel qui fait surface comme ça, pour ensuite mettre dans le front il pour gérer la consultation oubliée à un ensemble de table clé étrangère.

J'espère que cela vous aidera.

1voto

Joey Morgan Points 241

Mon problème était similaire, et il s'est avéré que je me le faisais à moi-même. J'exécutais un tas de scripts dans un projet VS2008 pour créer des procédures stockées. Dans l'une des procédures, j'ai utilisé une transaction. Le scripts exécutait la création de la proc à l'intérieur d'une transaction, plutôt que d'inclure le code de la transaction comme faisant partie de la procédure. Ainsi, lorsqu'il arrivait à la fin sans erreur, il engageait la transaction pour le scripts. Le code .Net utilisait également et engageait ensuite une transaction, et l'effet zombie est apparu lorsqu'il a essayé de fermer la transaction qui avait déjà été fermée à l'intérieur du scripts SQL. J'ai supprimé la transaction du SQL scripts, en me basant sur la transaction ouverte et vérifiée dans le code .Net, et cela a résolu le problème.

0voto

Vous avez prouvé que vos données sont correctes au-delà de tout doute raisonnable.
POUR INFO, je préférerais déplacer l'insertion dans un SPROC et ne pas utiliser la transaction du tout.
Si vous avez besoin que l'interface utilisateur soit réactive, utilisez un travailleur en arrière-plan pour effectuer le grunt de la base de données.
Pour moi, une transaction est destinée à des activités interdépendantes, pas à un dispositif permettant de gagner du temps. Le coût d'insertion doit être payé quelque part dans la chaîne.

J'ai récemment utilisé le profileur ANTS sur une application de base de données et j'ai été étonné de voir des exceptions intermittentes de SQlClient dans un code très performant. Les erreurs sont profondes dans le framework lors de l'ouverture d'une connexion. Elles ne remontent jamais à la surface et ne sont pas détectables par le code client. Donc... Le but ?
Tout n'est pas solide comme le roc, déplacez le travail lourd hors de l'I.U. et acceptez le coût. HTH Bob

0voto

Frank Myat Thu Points 803

J'ai également rencontré le même problème.

This SqlTransaction has completed; it is no longer usable

Après avoir fait quelques recherches, j'ai découvert que la taille du fichier journal de ma base de données est de 40 Go.
C'est le grand volume de données qui fait que ma transaction sql n'est pas validée.

Donc, ma solution est de shrink la taille du fichier journal en voyant ce lien de référence .

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

Puis l'exécuter EXEC sp_ShrinkLog_ByDataBaseName 'Yor_DB_NAME' .

Si cela ne fonctionne pas pour vous, voici ce qui suit une autre solution ce qui, je pense, va fonctionner.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X