2009-03-04 19 views
24

Mam aplikację, która potencjalnie wykonuje tysiące wstawek do bazy danych SQL Server 2005. Jeśli wstawka z jakiegoś powodu nie powiedzie się (ograniczenie klucza obcego, długość pola itp.), Aplikacja jest zaprojektowana do rejestrowania błędu wstawiania i kontynuowania.SqlTransaction zakończył

Każda wstawka jest niezależna od pozostałych, więc transakcje nie są potrzebne dla integralności bazy danych. Jednak chcemy je wykorzystać do zwiększenia wydajności. Kiedy korzystam z transakcji, otrzymamy następujący błąd na około 1 na każde 100 zatwierdzeń.

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

Aby spróbować wyśledzić przyczyny umieściłem oświadczenia w każdej operacji transakcji prześledzić więc mogę zapewnić, że transakcja nie była zamknięta przed wywołaniem popełnić. Potwierdziłem, że moja aplikacja nie kończy transakcji. Następnie uruchomiłem aplikację ponownie, używając dokładnie tych samych danych wejściowych i udało się.

Jeśli wyłączysz wylogowanie, ponownie się nie powiedzie. Włącz go i to się uda. Przełączanie włączania/wyłączania odbywa się za pomocą pliku app.config bez potrzeby ponownej kompilacji.

Oczywiście rejestracja zmienia czas i powoduje jego działanie. To wskazywałoby na problem z gwintowaniem. Jednak moja aplikacja nie jest wielowątkowa.

Widziałem jeden wpis MS KB wskazujący na błąd związany ze środowiskiem .Net 2.0 może powodować podobne problemy (http://support.microsoft.com/kb/912732). Jednak dostarczona przez nich poprawka nie rozwiązuje tego problemu.

+2

Użyj transakcji "dla zwiększenia wydajności"? W jaki sposób wykorzystanie transakcji poprawia wydajność? – LukeH

+0

Uzgodniono z Łukaszem, dlaczego widzisz lub myślisz, że masz taki efekt? – eglasius

+8

posiadanie transakcji wokół wielu wkładek ogólnie zwiększa wydajność, testuj sam i zobacz. poprzez transakcję zmniejszasz liczbę blokad, które musisz zdobyć przed wstawianiem. –

Odpowiedz

7

Trudno uzyskać pomoc, nie widząc kodu. Zakładam, że z twojego opisu korzystasz z transakcji do zatwierdzenia po każdym wstawieniu N, co poprawi wydajność w porównaniu z każdą wstawioną wstawką N nie jest zbyt duża.

Ale wadą jest to, że: jeśli wkładka się nie powiedzie, wszelkie inne wkładki w bieżącej partii N zostaną wycofane po wycofaniu transakcji.

Generalnie należy zlikwidować transakcję przed zamknięciem połączenia (co spowoduje wycofanie transakcji, jeśli nie została zatwierdzona). Zwykły wzór wygląda tak:

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 

Proszę napisać kod, jeśli potrzebujesz więcej pomocy.

+0

Zwykle używam tego modelu. Jednak w tym przypadku nie chcę, aby inne operacje zostały wycofane. Chcę tylko zarejestrować problem i wznowić. Transakcja jest wykorzystywana tylko jako zwiększenie wydajności. – aef123

6

Należy pamiętać, że aplikacja nie jest jedynym uczestnikiem transakcji - dotyczy to również programu SQL Server.

Błąd cytujesz:

Ten SqlTransaction zakończył; to nie jest już używane. w System.Data.SqlClient.SqlTransaction.ZombieCheck() w System.Data.SqlClient.SqlTransaction.Commit()

nie wskazuje transakcję comitted, tylko że jest on kompletny.

Moja pierwsza sugestia jest taka, że ​​serwer zabił transakcję, ponieważ trwało to zbyt długo (czas ścianki) lub było zbyt duże (zbyt wiele zmian lub zbyt wiele blokad).

Moja druga sugestia to sprawdzenie, czy poprawne są połączenia i transakcje.Możliwe, że napotykasz problemy, ponieważ okazjonalnie wyczerpujesz pulę niektórych zasobów, zanim coś zostanie automatycznie przetworzone.

Na przykład DbConnection realizuje IDisposable, więc trzeba upewnić się oczyścić odpowiednio - z using rachunku, jeśli możesz, lub dzwoniąc Dispose() bezpośrednio, jeśli nie można. 'DbCommand' jest podobny, ponieważ implementuje także IDisposable.

1

Cóż, najpierw IMO, nie powinieneś oczekiwać, że Twoja aplikacja poradzi sobie z "twardymi" błędami. Twoja aplikacja powinna zrozumieć, jakie są reguły biznesowe i wziąć je pod uwagę. NIE wymuszaj, aby baza danych była regułą biznesową lub policjantem reguły ograniczającej. Powinien to być tylko gliniarz reguł danych, a przy tym wdzięczny jest przekazywanie interfejsu za pomocą RETURN i innych metod. Schematy nie powinny być tak silne.

Po to, aby odpowiedzieć na twoje pytanie, podejrzewam, nie widząc żadnego kodu, że próbujesz wykonać commit, gdy wystąpił błąd, a jeszcze go nie znasz. Takie jest uzasadnienie mojego pierwszego zdania ... próbując złapać pułapkę za błąd, jaki daje baza danych, bez konieczności rozumienia i udziału w tych zasadach, dajesz sobie ból głowy.

Spróbuj tego. Wstaw wiersze, które nie będzie zawieść z ograniczeniem bazy danych lub inne błędy i przetwarzać wstawki z zatwierdzeniem. Zobacz co się dzieje. Następnie wpadnij w jakieś rekordy, które nie powiodą się, przeprowadź zatwierdzenie i sprawdź, czy otrzymasz piękny błąd. Po trzecie uruchom ponownie błędy, wykonaj wymuszone wycofanie i sprawdź, czy Ci się udało.

Kilka pomysłów. Ponownie, jako podsumowanie, myślę, że ma to związek z tym, że nie przechwytuje pewnych błędów z bazy danych w sposób wdzięczny za rzeczy, które są "ciężkimi" błędami i spodziewa się, że front zajmuje się nimi. Ponownie moja ekspertyza, NOPE, nie rób tego. To bałagan. Twoja aplikacja musi się pokrywać wiedzą o regułach z tyłu. Te rzeczy są na miejscu tylko po to, aby upewnić się, że tak się nie stanie podczas testowania, i od czasu do czasu błąd, który pojawia się w ten sposób, aby następnie umieścić go z przodu, aby obsłużyć zapomniane odnośniki do obcego zestawu tabel kluczy.

Mam nadzieję, że to pomaga.

+0

Zazwyczaj zgadzam się z Tobą na temat aplikacji, która zajmuje się regułami biznesowymi. Jednak w tym przypadku aplikacja jest narzędziem do konwersji danych. Użytkownik końcowy konfiguruje wszystkie reguły biznesowe. Użytkownik jest odpowiedzialny za zbudowanie konwersji w sposób akceptowany przez DB. Dlatego muszę się zalogować i kontynuować – aef123

+0

Zgadzam się również. Moja odpowiedź brzmi: popieram przyczynę problemu. Aplikacja nie uwzględniła błędu błędu IMO. Wykonaj zapisany proces jako instrukcję w SQL Enterprise Manager (pomijając aplikację) i sprawdź, jakie wyniki są skuteczne, czy nie. – SnapJag

0

Udowodniłeś, że twoje dane są w porządku poza wszelkimi uzasadnionymi wątpliwościami.
FWIW, Wolałbym przenieść insert do SPROC i nie używać w ogóle transakcji.
Jeśli potrzebujesz interfejsu do reagowania, użyj robota w tle, aby wykonać pomruk bazy danych.
Dla mnie transakcja dotyczy działań powiązanych, a nie oszczędzających czas. Koszt wstawienia musi zostać opłacony gdzieś wzdłuż linii.

Ostatnio użyłem profilera ANTS w aplikacji bazy danych i byłem zaskoczony, widząc przerywane wyjątki SQlClient pokazujące w solidnie wykonującym się kodzie. Błędy są głęboko w strukturze podczas otwierania połączenia. Nigdy nie trafiają na powierzchnię i nie są wykrywalne przez kod klienta. Więc ... Punkt?
To nie wszystko jest solidne, przenieś ciężką pracę z U.I. i zaakceptuj koszt. HTH Bob

7

Dzięki za wszystkie opinie. Pracowałem z kimś z MSFT na forum MSDN, aby dowiedzieć się, co się dzieje. Okazuje się, że przyczyną problemu jest jedna z wkładek, które uległy awarii z powodu problemu z konwersją daty.

Głównym problemem jest to, że ten błąd pojawia się, jeśli jest to błąd konwersji daty. Jeśli jednak jest to kolejny błąd, na przykład pole jest zbyt długie, nie powoduje to problemu. W obu przypadkach spodziewałbym się, że transakcja nadal będzie istniała, więc będę mógł nazywać ją Rollback.

Mam pełny przykładowy program do replikacji tego problemu. Jeśli ktoś chce to zobaczyć lub wymienić z MSFT, możesz znaleźć wątek na grupach dyskusyjnych MSFT w pliku microsoft.public.dotnet.framework.adonet pod wątkiem błędu SqlTransaction.ZombieCheck.

+3

Ostateczny wynik był taki, że SQL Server automatycznie wycofuje transakcje z powodu poważnych błędów. Błąd konwersji DateTime jest uważany za jeden z tych poważnych błędów. Obecnie nie ma sposobu, aby zatrzymać SQL Server od zakończenia transakcji dla tego rodzaju błędu. – aef123

3

Ten wyjątek jest zgłaszany, ponieważ faktyczna transakcja DB jest już wycofana, więc obiekt .NET reprezentujący ją po stronie klienta jest już "zombie".

Bardziej szczegółowe wyjaśnienie to here. This post wyjaśnia, jak napisać poprawny kod wycofania transakcji dla takich scenariuszy.

1

Mój problem był podobny i okazało się, że robię to dla siebie. Uruchomiłem kilka skryptów w projekcie VS2008, aby utworzyć procedury składowane. W jednym z proców użyłem transakcji. Skrypt wykonywał tworzenie procesu wewnątrz transakcji, a nie zawierał kod transakcji jako część procedury. Więc kiedy dotarło do końca bez błędu, zatwierdził transakcję dla skryptu. Kod .Net również wykorzystywał i zatwierdzał transakcję, a efekt zombie pojawił się, gdy próbował zamknąć transakcję, która została już zamknięta w skrypcie SQL. Usunąłem transakcję ze skryptu SQL, opierając się na transakcji otwartej i sprawdzonej w kodzie .Net, co rozwiązało problem.

1

Według tego postu: 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

tymczasowe rozwiązanie może być próba łapania wycofywania lub popełnić operację. Więc ten kod będzie wystarczające do zatrzymania bakcyla do rzucania:

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

Sprawdź ten przykład ze strony MSDN: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqltransaction.aspx

0

I również w obliczu tego samego problemu.

This SqlTransaction has completed; it is no longer usable 

Po wykonaniu niektórych badań okazało się, że rozmiar pliku logu mojej bazy danych wynosi 40 GB.
Jest to duży wolumin danych, który powoduje, że moja transakcja sql jest niezaakceptowana.

Moje rozwiązanie polega na rozmiarze pliku logu shrink przez zobaczenie 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 

Następnie wykonaj to EXEC sp_ShrinkLog_ByDataBaseName 'Yor_DB_NAME'.

Jeśli to nie działa, tutaj jest another solution, co moim zdaniem zadziała.

Powiązane problemy