Poprosiliśmy o nieużywanie transakcji i użycie obejścia w celu ich symulacji

43

Rozwijam T-SQL od kilku lat i zawsze zagłębiam się dalej, wciąż ucząc się wszystkiego, co mogę, o wszystkich aspektach języka. Niedawno zacząłem pracować w nowej firmie i otrzymałem dziwną sugestię dotyczącą transakcji. Nigdy ich nie używaj. Zamiast tego użyj obejścia, które symuluje transakcję. To pochodzi od naszego DBA, który pracuje w jednej bazie danych z dużą ilością transakcji, a następnie z dużą ilością blokowania. Baza danych, w której przede wszystkim pracuję, nie ma tego problemu i widzę, że transakcje były używane w przeszłości.

Rozumiem, że oczekuje się blokowania transakcji, ponieważ jest to w ich naturze i jeśli możesz uciec bez korzystania z nich, zrób to. Ale mam wiele okazji, w których każda instrukcja MUSI zostać wykonana pomyślnie. Jeśli ktoś zawiedzie, wszyscy muszą się nie popełnić.

Zawsze utrzymywałem jak najmniejszy zakres moich transakcji, zawsze używany w połączeniu z SET XACT_ABORT ON i zawsze w ramach TRY / CATCH.

Przykład:

CREATE SCHEMA someschema;
GO


CREATE TABLE someschema.tableA
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColA VARCHAR(10) NOT NULL
);
GO

CREATE TABLE someschema.tableB
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColB VARCHAR(10) NOT NULL
); 
GO


CREATE PROCEDURE someschema.ProcedureName @ColA VARCHAR(10), 
                                          @ColB VARCHAR(10)
AS
SET NOCOUNT, XACT_ABORT ON;
BEGIN
BEGIN TRY
    BEGIN TRANSACTION;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);

--Implement error
    SELECT 1/0 

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF @@trancount > 0
    BEGIN
        ROLLBACK TRANSACTION;
    END;
    THROW;
    RETURN;
END CATCH;
END;
GO

Oto, co zasugerowali mi zrobić.

GO



CREATE PROCEDURE someschema.ProcedureNameNoTransaction @ColA VARCHAR(10), 
                                                       @ColB VARCHAR(10)
AS
SET NOCOUNT ON;
BEGIN
BEGIN TRY
    DECLARE @tableAid INT;
    DECLARE @tableBid INT;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);
    SET @tableAid = SCOPE_IDENTITY();

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);
    SET @tableBid = SCOPE_IDENTITY();

--Implement error
    SELECT 1/0 

END TRY
BEGIN CATCH
    DELETE FROM someschema.tableA
    WHERE id = @tableAid;

    DELETE FROM someschema.tableB
    WHERE id = @tableBid;

    THROW;

    RETURN;
END CATCH;
END;
GO

Moje pytanie do społeczności jest następujące. Czy ma to sens jako praktyczne obejście transakcji?

Moim zdaniem z tego, co wiem o transakcjach i propozycji rozwiązania, jest to, że nie, nie jest to realne rozwiązanie i wprowadza wiele punktów niepowodzenia.

W sugerowanym obejściu widzę cztery niejawne transakcje. Dwie wstawki w try, a następnie dwie kolejne transakcje usuwania w catch. Robi to „cofanie” wkładek, ale bez cofania niczego, więc nic nie jest cofane.

Jest to bardzo podstawowy przykład, aby zademonstrować proponowaną przez siebie koncepcję. Niektóre z rzeczywistych procedur przechowywanych, w których to robię, sprawiają, że są one wyjątkowo długie i trudne do zarządzania, ponieważ „wycofywanie” wielu zestawów wyników w porównaniu z dwoma wartościami parametrów w tym przykładzie staje się dość skomplikowane, jak można sobie wyobrazić. Ponieważ „wycofywanie” odbywa się teraz ręcznie, możliwość przegapienia czegoś, ponieważ jest realna.

Innym problemem, który moim zdaniem istnieje, są przekroczenia limitu czasu lub zerwane połączenia. Czy to wciąż się wycofuje? Rozumiem, dlaczego należy użyć SET XACT_ABORT ON, aby w takich przypadkach transakcja została wycofana.

Z góry dziękuję za opinię!

Forrest
źródło
4
Komentarze, które nie spełniają podanego celu , zostały usunięte lub przeniesione do odpowiedzi na Wiki społeczności.
Paul White

Odpowiedzi:

61

Nie można nie wykorzystywać transakcje w SQL Server (i zapewne każdy inny właściwy RDBMS). W przypadku braku wyraźnych granic transakcji ( begin transaction... commit) każda instrukcja SQL rozpoczyna nową transakcję, która jest domyślnie zatwierdzona (lub wycofana) po zakończeniu (lub niepowodzeniu) instrukcji.

Symulacja transakcji sugerowana przez osobę, która przedstawia się jako „DBA”, nie zapewnia trzech z czterech wymaganych właściwości przetwarzania transakcji, ponieważ dotyczy tylko „miękkich” błędów i nie jest w stanie poradzić sobie z „twardymi” błędami, takich jak rozłączenia sieci, przerwy w dostawie prądu, awarie dysków i tak dalej.

  • Atomowość: porażka. Jeśli „twardy” błąd wystąpi gdzieś w środku pseudo-transakcji, zmiana nie będzie atomowa.

  • Spójność: niepowodzenie. Z powyższego wynika, że ​​Twoje dane będą w niespójnym stanie po wystąpieniu „twardego” błędu.

  • Izolacja: niepowodzenie. Możliwe jest, że współbieżna pseudotransakcja zmienia niektóre dane zmodyfikowane przez twoją pseudotransakcję przed zakończeniem twoich.

  • Trwałość: sukces. Wprowadzone zmiany będą trwałe, serwer bazy danych to zapewni; to jedyna rzecz, której podejście twojego kolegi nie może spieprzyć.

Zamki są powszechnie stosowaną i empirycznie skuteczną metodą zapewniania ACID-y transakcji wszelkiego rodzaju lub RDBMS (ta strona jest przykładem). Uważam za bardzo mało prawdopodobne, aby przypadkowa DBA mogła znaleźć lepsze rozwiązanie problemu współbieżności niż setki, być może tysiące informatyków i inżynierów, którzy budowali ciekawe systemy baz danych w ciągu ostatnich 50 lat? 60 lat? (Zdaję sobie sprawę, że jest to nieco mylące jako argument „odwoływania się do władzy”, ale mimo to będę go trzymać).

Podsumowując, zignoruj ​​radę „DBA”, jeśli możesz, walcz z nią, jeśli masz ducha, i wróć tutaj z konkretnymi problemami dotyczącymi współbieżności, jeśli się pojawią.

mustaccio
źródło
14

Istnieje kilka błędów, które są tak poważne, że blok CATCH nigdy nie jest wprowadzany. Z dokumentacji

Błędy o istotności 20 lub wyższej, które zatrzymują przetwarzanie zadania aparatu bazy danych programu SQL Server dla sesji. Jeśli wystąpi błąd o istotności 20 lub wyższej, a połączenie z bazą danych nie zostanie zakłócone, SPRÓBUJ ... POŁÓW zajmie się błędem.

Uwagi, takie jak żądania przerwania klienta lub przerwane połączenia klienta.

Gdy sesja zostanie zakończona przez administratora systemu za pomocą instrukcji KILL.

...

Błędy kompilacji, takie jak błędy składniowe, które uniemożliwiają uruchomienie partii.

Błędy, które występują ... z powodu odroczonego rozpoznawania nazw.

Wiele z nich można łatwo wygenerować za pomocą dynamicznego SQL. Wycofane instrukcje, takie jak pokazane, nie będą chronić danych przed takimi błędami.

Michael Green
źródło
2
Zgadza się - a jeśli nic innego, klient umierający podczas wykonywania kodu stanowiłby błąd „tak poważny, że blok CATCH nigdy nie jest wprowadzany”. Bez względu na to, jak bardzo ufasz oprogramowaniu (nie tylko własnemu kodowi, ale KAŻDEJ części WSZYSTKICH stosów oprogramowania), zawsze istnieje możliwość awarii sprzętu (ponownie, potencjalnie w dowolnym miejscu w łańcuchu), zatrzymując Cię w każdej chwili . Mając to na uwadze, stanowi dobrą obronę przed życzliwym myśleniem, które prowadzi do tego rodzaju „obejścia”.
dgould
2
Ponadto możesz być ofiarą impasu. Twoje bloki CATCH działają, ale rzucają, jeśli próbują zapisywać w bazie danych.
Jozuego
10

i-one : Sugerowane obejście umożliwia (przynajmniej) naruszenie „A” ACID . Na przykład, jeśli SP jest wykonywane przez klienta zdalnego, a połączenie zostaje zerwane, może dojść do częściowego „zatwierdzenia” / „wycofania”, ponieważ serwer może zakończyć sesję między dwoma wstawieniami / usunięciami (i przerwać wykonywanie SP, zanim osiągnie swój koniec) .

Czy ma to sens jako praktyczne obejście transakcji?

dan-guzman : Nie,CATCHblok nigdy nie zostanie wykonany w przypadku przekroczenia limitu czasu zapytania, ponieważ interfejs API klienta anulował partię. Bez transakcjiSET XACT_ABORT ONnie można wycofać niczego poza bieżącym wyciągiem.

tibor-karaszi : Masz 4 transakcje, co oznacza więcej logowania do pliku dziennika transakcji. Pamiętaj, że każda transakcja wymaga synchronicznego zapisu rekordów dziennika do tego momentu, tj. Pogorszysz wydajność również z tego aspektu, gdy używasz wielu transakcji.

rbarryyoung : Jeśli często blokują, muszą albo poprawić projekt danych, zracjonalizować porządek dostępu do tabeli lub zastosować bardziej odpowiedni poziom izolacji. Zakładają, że ich problemy (i ich niezrozumienie) staną się twoim problemem. Dowody z milionów innych baz danych wskazują, że tak nie będzie.

Ponadto to, co próbują ręcznie wdrożyć, to w rzeczywistości optymistyczna współbieżność. Zamiast tego powinni użyć jednej z najlepszych optymistycznych współbieżności na świecie, już wbudowanej w SQL Server. To idzie do punktu izolacji powyżej. Najprawdopodobniej muszą przejść z jakiegokolwiek pesymistycznego poziomu izolacji współbieżności, którego używają obecnie, na jeden z optymistycznych poziomów izolacji współbieżności SNAPSHOTlub READ_COMMITTED_SNAPSHOT. Skutecznie zrobią to samo, co kod manualny, tyle że zrobi to poprawnie.

ross-presser : Jeśli masz wyjątkowo długie procesy - jak coś dzieje się dzisiaj i w przyszłym tygodniu coś musi nastąpić, a jeśli coś w przyszłym tygodniu się nie powiedzie, dzisiejsze musi zawieść z mocą wsteczną - możesz zajrzeć do sag . Ściśle mówiąc, jest to poza bazą danych, ponieważ wymaga magistrali usług.

rev użytkownik126897
źródło
5

Kod złego pomysłu będzie po prostu droższy, aby naprawić linię.

Jeśli występują problemy z blokowaniem przy użyciu jawnej transakcji (wycofanie / zatwierdzenie), skieruj swoją DBA do Internetu, aby znaleźć kilka świetnych pomysłów na rozwiązanie problemów.

Oto sposób, aby pomóc złagodzić blokowanie: https://www.sqlservercentral.com/articles/using-indexes-to-reduce-blocking-in-concurrent-transactions

Indeksy zmniejszają liczbę wyszukiwań, które muszą wystąpić w tabeli / stronie, aby znaleźć wiersz / zestaw wierszy. Są one ogólnie postrzegane jako metoda skrócenia czasu wykonywania zapytań SELECT * i słusznie. Nie są uważane za odpowiednie dla tabel zaangażowanych w dużą liczbę AKTUALIZACJI. W rzeczywistości okazuje się, że INDEKSY są niekorzystne w tych przypadkach, ponieważ zwiększają czas potrzebny na wypełnienie zapytań UPDATE.

Lecz nie zawsze tak jest. Zagłębiając się nieco w wykonanie instrukcji UPDATE, okazuje się, że ona również wymaga najpierw wykonania instrukcji SELECT. Jest to specjalny i często spotykany scenariusz, w którym zapytania aktualizują wzajemnie wykluczające się zestawy wierszy. INDEKSY tutaj mogą prowadzić do znacznego wzrostu wydajności silnika bazy danych wbrew powszechnemu przekonaniu.

użytkownik238855
źródło
4

Fałszywa strategia transakcji jest niebezpieczna, ponieważ pozwala na problemy z współbieżnością, którym konkretnie zapobiegają transakcje. Weź pod uwagę, że w drugim przykładzie dowolne dane mogą być zmieniane między instrukcjami.

Fałszywe usunięcie transakcji nie jest GWARANTOWANE do uruchomienia lub powodzenia. Jeśli serwer bazy danych wyłączy się podczas fałszywej transakcji, niektóre, ale nie wszystkie, efekty pozostaną. Nie gwarantuje się również, że odniosą sukces w taki sposób, jak w przypadku wycofania transakcji.

Ta strategia może działać z wstawkami, ale na pewno nie zadziała z aktualizacjami lub usunięciami (bez instrukcji SQL maszyny czasu).

Jeśli ścisła współbieżność transakcji powoduje blokowanie, istnieje wiele rozwiązań, nawet takich, które obniżają poziom ochrony ... są to właściwy sposób rozwiązania problemu.

Twój DBA oferuje rozwiązanie, które może działać OK, jeśli byłby tylko jeden użytkownik bazy danych, ale absolutnie nie nadaje się do jakiegokolwiek poważnego użycia.

Bailey S.
źródło
4

To nie jest problem programistyczny, a raczej problem interpersonalny / nieporozumienia. Najprawdopodobniej Twój „DBA” martwi się blokadami, a nie transakcjami.

Inne odpowiedzi już wyjaśniają, dlaczego musisz korzystać z transakcji ... Mam na myśli to, co robi RDBMS, bez poprawnie używanych transakcji nie ma integralności danych, więc skupię się na tym, jak rozwiązać prawdziwy problem, czyli: dowiedzieć się, dlaczego twój „DBA” rozwinął alergię na transakcje i przekonał go, by zmienił zdanie.

Myślę, że ten facet myli „szczególny scenariusz, w którym zły kod spowodował straszną wydajność” z „wszystkie transakcje są złe”. Nie spodziewałbym się, że kompetentny DBA popełni ten błąd, więc to naprawdę dziwne. Może miał naprawdę złe doświadczenia z jakimś okropnym kodem?

Rozważ taki scenariusz:

BEGIN
UPDATE or DELETE some row, which takes locks it
...do something that takes a while
...perform other queries
COMMIT

Ten styl użycia transakcji zawiera blokadę (lub kilka blokad), co oznacza, że ​​inne transakcje uderzające w te same wiersze będą musiały poczekać. Jeśli blokady są utrzymywane przez długi czas, a zwłaszcza jeśli wiele innych transakcji chce zablokować te same wiersze, może to naprawdę pogorszyć wydajność.

Możesz go zapytać, dlaczego ma ten dziwnie błędny pomysł, że nie używa transakcji, jakie rodzaje zapytań były problematyczne itp. Następnie spróbuj go przekonać, że na pewno unikniesz podobnych złych scenariuszy, że będziesz monitorować użycie zamka i występ, uspokój go itp.

Mówi ci: „nie dotykaj śrubokręta!” więc kod, który opublikowałeś w swoim pytaniu, to w zasadzie użycie młotka do wkręcenia śruby. O wiele lepszą opcją jest przekonanie go, że umiesz używać śrubokręta ...

Mogę wymyślić kilka przykładów ... cóż, były one na MySQL, ale to też powinno działać.

Było forum, na którym aktualizacja pełnego tekstu trwała chwilę. Gdy użytkownik prześle post, transakcja zaktualizuje tabelę tematów, aby zwiększyć liczbę postów i datę ostatniego postu (blokując w ten sposób wiersz tematu), a następnie wstawi post, a transakcja będzie blokować, dopóki indeks pełnotekstowy nie zakończy aktualizacji i COMMIT został zakończony.

Ponieważ działało to na Rustbucket ze zbyt małą pamięcią RAM, aktualizacja tego indeksu pełnotekstowego często powodowała kilka sekund intensywnego losowego we / wy na pojedynczym, wolno obracającym się napędzie w pudełku.

Problem polegał na tym, że osoby, które kliknęły ten temat, spowodowały, że zapytanie zwiększyło liczbę wyświetleń tego tematu, co również wymagało zablokowania wiersza tematu. Dlatego nikt nie mógł wyświetlić tematu podczas aktualizacji indeksu pełnotekstowego. Mam na myśli, że wiersz można odczytać, ale jego aktualizacja się zablokuje.

Co gorsza, publikowanie zaktualizowałoby liczbę postów w tabeli forów nadrzędnych i wstrzymało blokadę podczas aktualizacji indeksu pełnotekstowego ... co spowodowało zamrożenie całego forum na kilka sekund i spowodowało, że w kolejce serwera WWW pojawiło się mnóstwo żądań .

Rozwiązaniem było przyjęcie blokad we właściwej kolejności: ROZPOCZNIJ, wstaw wpis i zaktualizuj indeks pełnotekstowy bez żadnych blokad, a następnie szybko zaktualizuj wiersze tematu / forum o liczbie postów i dacie ostatniego postu oraz ZAPISZ. To całkowicie rozwiązało problem. Poruszałem się po kilku zapytaniach, naprawdę proste.

W tym przypadku transakcje nie stanowiły problemu ... Pozyskiwanie niepotrzebnej blokady przed długą operacją. Inne przykłady rzeczy, których należy unikać, trzymając blokadę w transakcji: oczekiwanie na dane wejściowe użytkownika, dostęp do wielu niebuforowanych danych z wolno wirujących dysków, sieciowe operacje we / wy itp.

Oczywiście czasami nie masz wyboru i musisz wykonać długie przetwarzanie, trzymając nieporęczne zamki. Są wokół tego pewne sztuczki (operowanie na kopii danych itp.), Ale dość często wąskie gardło wydajności pochodzi z zamka, który nie został celowo uzyskany, a po prostu zmiana kolejności zapytań rozwiązuje problem. Co więcej, jest świadomy blokad podjętych podczas pisania zapytań ...

Nie powtórzę innych odpowiedzi, ale naprawdę ... używam transakcji. Twoim problemem jest przekonanie „DBA”, a nie obejście najważniejszej funkcji bazy danych ...

peufeu
źródło
3

TLDR: Użyj właściwego poziomu izolacji .

Jak prawidłowo zauważyłeś, podejście bez transakcji i z „ręcznym” odzyskiwaniem może być bardzo złożone. Wysoka złożoność oznacza zwykle znacznie więcej czasu na wdrożenie i znacznie więcej czasu na naprawę błędów (ponieważ złożoność prowadzi do większej liczby błędów we wdrażaniu). Oznacza to, że takie podejście może znacznie kosztować klienta.

Głównym problemem twojego kolegi z "dba" jest wydajność. Jednym ze sposobów, aby to poprawić, jest zastosowanie odpowiedniego poziomu izolacji. Załóżmy, że masz procedurę, która zapewnia użytkownikowi dane przeglądowe. W takiej procedurze niekoniecznie trzeba stosować poziom izolacji SERIALIZABLE. W wielu przypadkach CZYTAJ NIEZAKOŃCZONE może być wystarczające. Oznacza to, że taka procedura nie zostanie zablokowana przez twoją transakcję, która tworzy lub modyfikuje niektóre dane.

Sugeruję przejrzenie wszystkich istniejących funkcji / procedur w bazie danych, ocenę rozsądnego poziomu izolacji dla każdej z nich, wyjaśnienie klientom korzyści związanych z wydajnością. Następnie odpowiednio dostosuj te funkcje / procedury.

mentallurg
źródło
2

Możesz także zdecydować się na użycie tabel OLTP w pamięci. Oczywiście nadal korzystają z transakcji, ale nie wiąże się to z blokowaniem.
Zamiast blokować wszystkie operacje zakończą się powodzeniem, ale podczas fazy zatwierdzania silnik sprawdzi konflikty transakcji i jeden z zatwierdzeń może się nie powieść. Microsoft używa terminu „optymistyczne blokowanie”.
Jeśli problem ze skalowaniem jest spowodowany konfliktem między dwiema operacjami zapisu, takimi jak dwie jednoczesne transakcje próbujące zaktualizować ten sam wiersz, OLTP w pamięci pozwala jednej transakcji zakończyć się sukcesem, a drugiej nie. Nieudana transakcja musi zostać przesłana ponownie jawnie lub niejawnie, ponawiając próbę transakcji.
Więcej na: W pamięci OLTP

Piotr
źródło
-5

Istnieje sposób obejścia korzystania z transakcji w ograniczonym zakresie, polegający na zmianie modelu danych na bardziej obiektowy. Zamiast przechowywać na przykład dane demograficzne o osobie w kilku tabelach i powiązać je ze sobą oraz wymagające transakcji, możesz mieć pojedynczy dokument JSON, który przechowuje wszystko, co wiesz o tej osobie w jednym polu. Oczywiście sprawdzanie, że domeny rozciągają się, jest kolejnym wyzwaniem projektowym, najlepiej wykonanym przez programistów, a nie DBA

użytkownik2127
źródło