Czy powinienem zatwierdzić czy wycofać transakcję odczytu?

96

Mam zapytanie odczytu, które wykonuję w ramach transakcji, aby określić poziom izolacji. Co mam zrobić po zakończeniu zapytania?

  • Zatwierdź transakcję
  • Wycofaj transakcję
  • Nic nie rób (co spowoduje wycofanie transakcji na końcu using bloku)

Jakie są konsekwencje wykonania każdego z nich?

using (IDbConnection connection = ConnectionFactory.CreateConnection())
{
    using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        using (IDbCommand command = connection.CreateCommand())
        {
            command.Transaction = transaction;
            command.CommandText = "SELECT * FROM SomeTable";
            using (IDataReader reader = command.ExecuteReader())
            {
                // Read the results
            }
        }

        // To commit, or not to commit?
    }
}

EDYCJA: Nie chodzi o to, czy transakcja powinna zostać użyta, czy też istnieją inne sposoby ustawienia poziomu transakcji. Pytanie brzmi, czy ma znaczenie, że transakcja, która niczego nie modyfikuje, jest zatwierdzana lub wycofywana. Czy jest różnica w wydajności? Czy ma to wpływ na inne połączenia? Jakieś inne różnice?

Stefan Moser
źródło
1
Prawdopodobnie już o tym wiesz, ale biorąc pod uwagę przykład, który podałeś, możesz uzyskać równoważne wyniki, wykonując proste zapytanie: SELECT * FROM SomeTable z NOLOCK
JasonTrue
@Stefan, wygląda na to, że większość z nas zastanawia się, dlaczego zawracasz sobie głowę transakcją tylko do odczytu. Czy możesz nas powiadomić, jeśli wiesz o NOLOCK, a jeśli tak, dlaczego nie wybrałeś tej trasy.
StingyJack
Wiem o NOLOCK, ale ten system działa na różnych bazach danych, a także na SQL Server, więc staram się unikać specyficznych podpowiedzi blokujących SQL Server. Jest to pytanie bardziej z ciekawości niż czegokolwiek innego, ponieważ aplikacja działa dobrze z powyższym kodem.
Stefan Moser
Ach, w takim przypadku usuwam tag sqlserver, ponieważ wskazuje on MSSqlServer jako produkt docelowy.
StingyJack
@StingyJack - Masz rację, nie powinienem był używać tagu sqlserver.
Stefan Moser

Odpowiedzi:

52

Zobowiązujesz się. Kropka. Nie ma innej rozsądnej alternatywy. Jeśli rozpocząłeś transakcję, powinieneś ją zamknąć. Zatwierdzenie zwalnia wszelkie blokady, które mogłeś mieć i jest równie sensowne z poziomami izolacji ReadUncommitted lub Serializable. Poleganie na niejawnym wycofywaniu zmian - choć może technicznie równoważne - jest po prostu kiepską formą.

Jeśli to cię nie przekonało, wyobraź sobie następnego faceta, który wstawia instrukcję aktualizacji w środku twojego kodu i musi wyśledzić niejawne wycofanie, które ma miejsce i usuwa jego dane.

Mark Brackett
źródło
45
Jest rozsądna alternatywa - wycofanie. To znaczy jawne wycofanie. Jeśli nie chciałeś niczego zmieniać, wycofywanie gwarantuje, że cokolwiek zostanie cofnięte. Oczywiście nie powinno być żadnych zmian; rollback to gwarantuje.
Jonathan Leffler
2
Różne DBMS mogą mieć różną semantykę „niejawnego zakończenia transakcji”. IBM Informix (i uważam, że DB2) dokonują niejawnego wycofywania; Według plotek Oracle dokonuje niejawnego zatwierdzenia. Wolę niejawne wycofywanie.
Jonathan Leffler
8
Załóżmy, że tworzę tabelę tymczasową, wypełniam ją identyfikatorami, łączę ją z tabelą danych, aby wybrać dane, które są powiązane z identyfikatorami, a następnie usuwam tabelę tymczasową. Naprawdę tylko czytam dane i nie obchodzi mnie, co stanie się z tabelą tymczasową, ponieważ jest tymczasowa ... ale z punktu widzenia wydajności, czy cofnięcie transakcji czy zatwierdzenie jej byłoby droższe? Jaki jest efekt zatwierdzenia / wycofania, gdy w grę wchodzą tylko tabele tymczasowe i operacje odczytu?
Triynko
4
@Triynko - Intuicyjnie sądzę, że ROLLBACK jest droższy. COMMIT to normalny przypadek użycia, a ROLLBACK to wyjątkowy przypadek. Ale kogo to obchodzi, poza akademickim? Jestem pewien, że istnieje 1000 lepszych punktów optymalizacji dla Twojej aplikacji. Jeśli jesteś naprawdę ciekawy, możesz znaleźć kod obsługi transakcji mySQL na bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/ ...
Mark Brackett
3
@Triynko - Jedynym sposobem optymalizacji jest profil. To taka prosta zmiana kodu, że nie ma powodu, aby nie profilować obu metod, jeśli naprawdę chcesz ją zoptymalizować. Nie zapomnij przekazać nam wyników!
Mark Brackett
28

Jeśli nic nie zmieniłeś, możesz użyć COMMIT lub ROLLBACK. Każda z nich zwolni wszystkie nabyte blokady odczytu, a ponieważ nie wprowadziłeś żadnych innych zmian, będą one równoważne.

Graeme Perrow
źródło
2
Dzięki za poinformowanie mnie, że są równoważne. Moim zdaniem to najlepiej odpowiada na rzeczywiste pytanie.
chowey
oznaczałoby to, że transakcja jest nieaktywna, jeśli użyjemy zatwierdzenia bez aktualnych aktualizacji. Właśnie spotkałem się z tym na mojej stronie na żywo
Muhammad Omer Aslam
6

Jeśli zaczynasz transakcję, najlepszą praktyką jest zawsze jej zatwierdzenie. Jeśli wyjątek zostanie zgłoszony w Twoim bloku użytkowania (transakcji), transakcja zostanie automatycznie wycofana.

Neil Barnwell
źródło
3

IMHO może mieć sens zawijanie zapytań tylko do odczytu w transakcje, ponieważ (szczególnie w Javie) można powiedzieć, że transakcja ma być tylko do odczytu, co z kolei sterownik JDBC może rozważyć optymalizację zapytania (ale nie musi, więc nikt uniemożliwi Ci INSERTjednak wydanie ). Np. Sterownik Oracle całkowicie uniknie blokad tabeli w zapytaniach w transakcji oznaczonej jako tylko do odczytu, co zapewnia dużą wydajność w aplikacjach o dużym stopniu odczytu.

Oliver Drotbohm
źródło
3

Rozważ transakcje zagnieżdżone .

Większość systemów RDBMS nie obsługuje transakcji zagnieżdżonych lub próbuje emulować je w bardzo ograniczony sposób.

Na przykład w MS SQL Server wycofanie w transakcji wewnętrznej (która nie jest prawdziwą transakcją, MS SQL Server liczy tylko poziomy transakcji!) Spowoduje wycofanie wszystkiego, co wydarzyło się w transakcji zewnętrznej (która jest transakcją rzeczywistą).

Niektóre opakowania baz danych mogą traktować wycofanie w transakcji wewnętrznej jako znak, że wystąpił błąd i wycofać wszystko w transakcji zewnętrznej, niezależnie od tego, czy transakcja zewnętrzna została zatwierdzona, czy wycofana.

Tak więc COMMIT jest bezpiecznym sposobem, kiedy nie można wykluczyć, że twój komponent jest używany przez jakiś moduł oprogramowania.

Zwróć uwagę, że jest to ogólna odpowiedź na pytanie. Przykładowy kod sprytnie rozwiązuje problem z transakcją zewnętrzną, otwierając nowe połączenie z bazą danych.

Jeśli chodzi o wydajność: w zależności od poziomu izolacji, elementy SELECT mogą wymagać różnego stopnia blokad LOCK i danych tymczasowych (migawek). Jest to czyszczone po zamknięciu transakcji. Nie ma znaczenia, czy odbywa się to poprzez COMMIT czy ROLLBACK. Może występować nieznaczna różnica w wydanym czasie procesora - polecenie COMMIT jest prawdopodobnie szybsze do przeanalizowania niż ROLLBACK (dwa znaki mniej) i inne drobne różnice. Oczywiście dotyczy to tylko operacji tylko do odczytu!

Całkowicie niewymagane: inny programista, który mógłby odczytać kod, mógłby założyć, że ROLLBACK oznacza stan błędu.

Klawy
źródło
2

Na marginesie, ale możesz też napisać ten kod w ten sposób:

using (IDbConnection connection = ConnectionFactory.CreateConnection())
using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
using (IDbCommand command = connection.CreateCommand())
{
    command.Transaction = transaction;
    command.CommandText = "SELECT * FROM SomeTable";
    using (IDataReader reader = command.ExecuteReader())
    {
        // Do something useful
    }
    // To commit, or not to commit?
}

A jeśli nieco zmienisz strukturę, możesz również przenieść blok using dla IDataReader na górę.

Joel Coehoorn
źródło
1

Jeśli umieścisz SQL w procedurze składowanej i dodasz to powyżej zapytania:

set transaction isolation level read uncommitted

wtedy nie musisz przeskakiwać przez żadne obręcze w kodzie C #. Ustawienie poziomu izolacji transakcji w procedurze składowanej nie powoduje zastosowania tego ustawienia do wszystkich przyszłych zastosowań tego połączenia (o co należy się martwić w przypadku innych ustawień, ponieważ połączenia są w puli). Pod koniec procedury składowanej po prostu wraca do tego, z czym połączenie zostało zainicjowane.

Eric Z Beard
źródło
1

ROLLBACK jest najczęściej używany w przypadku błędu lub wyjątkowych okoliczności, a COMMIT w przypadku pomyślnego zakończenia.

Powinniśmy zamykać transakcje za pomocą COMMIT (dla sukcesu) i ROLLBACK (dla niepowodzenia), nawet w przypadku transakcji tylko do odczytu, gdzie nie wydaje się to mieć znaczenia. W rzeczywistości ma to znaczenie dla spójności i przyszłości.

Transakcja tylko do odczytu może logicznie „zakończyć się niepowodzeniem” na wiele sposobów, na przykład:

  • zapytanie nie zwraca dokładnie jednego wiersza zgodnie z oczekiwaniami
  • procedura składowana zgłasza wyjątek
  • pobrane dane są niespójne
  • użytkownik przerywa transakcję, ponieważ trwa to zbyt długo
  • zakleszczenie lub przekroczenie limitu czasu

Jeśli polecenia COMMIT i ROLLBACK są używane prawidłowo dla transakcji tylko do odczytu, będą nadal działać zgodnie z oczekiwaniami, jeśli w pewnym momencie zostanie dodany kod zapisu DB, np. W celu buforowania, audytu lub statystyk.

Niejawne ROLLBACK powinno być używane tylko w sytuacjach „krytycznego błędu”, gdy aplikacja ulega awarii lub kończy pracę z nieodwracalnym błędem, awarią sieci, awarią zasilania itp.

Sam Watkins
źródło
0

Biorąc pod uwagę, że READ nie zmienia stanu, nie zrobiłbym nic. Wykonanie zatwierdzenia nic nie da, poza zmarnowaniem cyklu na wysłanie żądania do bazy danych. Nie wykonałeś operacji, która zmieniła stan. Podobnie jest w przypadku wycofywania.

Należy jednak pamiętać o wyczyszczeniu obiektów i zamknięciu połączeń z bazą danych. Brak zamykania połączeń może prowadzić do problemów, jeśli ten kod jest wywoływany wielokrotnie.

Brett McCann
źródło
3
W zależności od poziomu izolacji, wybrana MOŻE uzyskać blokady, które będą blokować inne transakcje.
Graeme Perrow
Połączenie zostanie zamknięte na końcu using bloku - do tego służy. Ale dobra uwaga, że ​​ruch sieciowy jest prawdopodobnie najwolniejszą częścią równania.
Joel Coehoorn
1
Transakcja zostanie zatwierdzona lub wycofana w taki czy inny sposób, więc najlepszą praktyką byłoby zawsze wydawanie zatwierdzenia, jeśli się powiedzie.
Neil Barnwell
0

Jeśli ustawisz AutoCommit false, a następnie TAK.

W eksperymencie z JDBC (sterownik Postgresql) odkryłem, że jeśli kwerenda wybierająca zrywa się (z powodu przekroczenia limitu czasu), nie można zainicjować nowego zapytania wybierającego, chyba że wycofasz wycofanie.

Shiv Krishna Jaiswal
źródło
-2

W przykładowym kodzie, gdzie masz

  1. // Zrób coś pożytecznego

    Czy wykonujesz instrukcję SQL, która zmienia dane?

Jeśli nie, nie ma czegoś takiego jak transakcja „Odczyt” ... Tylko zmiany z instrukcji Insert, Update i Delete (instrukcje, które mogą zmieniać dane) są w transakcji ... To, o czym mówisz, to blokady, które SQL Serwer zapisuje dane, które czytasz, z powodu INNYCH transakcji, które mają wpływ na te dane. Poziom tych blokad zależy od poziomu izolacji SQL Server.

Ale nie możesz Commit lub ROll Back niczego, jeśli twoja instrukcja SQL niczego nie zmieniła.

Jeśli zmieniasz dane, możesz zmienić poziom odseparowania bez jawnego rozpoczynania transakcji… Każda pojedyncza instrukcja SQL jest niejawnie zawarta w transakcji. jawne rozpoczęcie transakcji jest konieczne tylko w celu upewnienia się, że w ramach tej samej transakcji znajdują się 2 lub więcej wyciągów.

Jeśli wszystko, co chcesz zrobić, to ustawić poziom izolacji transakcji, a następnie ustawić tekst polecenia polecenia na „Ustaw poziom izolacji transakcji powtarzalny” (lub dowolny inny poziom), ustaw CommandType na CommandType.Text i wykonaj polecenie. (możesz użyć Command.ExecuteNonQuery ())

UWAGA: Jeśli wykonujesz WIELOKROTNE instrukcje odczytu i chcesz, aby wszystkie „widziały” ten sam stan bazy danych, co pierwsza, musisz ustawić poziom izolacji Odczyt powtarzalny lub serializowalny ...

Charles Bretana
źródło
// Zrób coś pożytecznego, nie zmieni żadnych danych, po prostu przeczytaj. Chcę tylko określić poziom odseparowania zapytania.
Stefan Moser
Następnie możesz to zrobić bez jawnego rozpoczynania transakcji od klienta ... Po prostu wykonaj ciąg sql „Ustaw poziom izolacji transakcji Odczyt niezatwierdzony”, „... Odczyt zatwierdzony”, „… RepeatableRead”, „... Migawka” lub „… z możliwością serializacji” „Ustaw poziom izolacji jako odczyt
zatwierdzony
3
Transakcje nadal mają znaczenie, nawet jeśli tylko czytasz. Jeśli chcesz wykonać kilka operacji odczytu, wykonanie ich wewnątrz transakcji zapewni spójność. Robienie ich bez jednego nie będzie.
MarkR
tak, przepraszam, masz rację, przynajmniej tak jest, jeśli poziom izolacji jest ustawiony na odczyt powtarzalny lub wyższy.
Charles Bretana
-3

Czy chcesz zablokować innym możliwość odczytu tych samych danych? Dlaczego warto skorzystać z transakcji?

@Joel - Moje pytanie byłoby lepiej sformułowane jako „Po co używać transakcji na przeczytanym zapytaniu?”

@Stefan - Jeśli zamierzasz używać AdHoc SQL, a nie przechowywanego procesu, po prostu dodaj WITH (NOLOCK) po tabelach w zapytaniu. W ten sposób nie ponosisz kosztów (choć minimalnych) w aplikacji i bazie danych dla transakcji.

SELECT * FROM SomeTable WITH (NOLOCK)

EDYCJA @ Komentarz 3: Ponieważ masz "sqlserver" w tagach pytania, założyłem, że MSSQLServer jest produktem docelowym. Teraz, gdy ten punkt został wyjaśniony, zmodyfikowałem tagi, aby usunąć odniesienie do konkretnego produktu.

Nadal nie jestem pewien, dlaczego w ogóle chcesz dokonać transakcji na opcjach odczytu.

StingyJack
źródło
1
Do ustawionego poziomu izolacji naraz. Możesz użyć transakcji, aby faktycznie zmniejszyć ilość blokad dla zapytania.
Joel Coehoorn
1
Używam transakcji, aby móc użyć niższego poziomu izolacji i zmniejszyć blokowanie.
Stefan Moser
@StingyJack - ten kod może być wykonywany na wielu różnych bazach danych, więc NOLOCK nie jest opcją.
Stefan Moser