Úvod
Velmi nedávno za mnou přišel můj kolega v zoufalství a přiznal, že vydal prohlášení o aktualizaci bez klauzule WHERE na tabulce klíčových aplikací. Důsledky na frontend by byly hrozivé, a tak přišel přímo za mnou, protože naléhavě potřeboval pomoci se zvrácením situace jakýmkoli způsobem, než se začaly hrnout e-maily a eskalace.
Když jsme se na situaci podívali, zjistili jsme, že změny nebyly použity v sekundární databázi. Ve většině případů je prodleva mezi naší primární a sekundární databází dvacet minut (máme trochu zarážející, abychom se vyhnuli problémům s výkonem). Protože kolega ihned po zjištění chyby požádal o pomoc, podařilo se nám obnovit data ze sekundární databáze. Hodnotu takového zpoždění jsem popsal v tomto článku .
Přezkoumání scénáře
Scénář, který jsem popsal výše, není neobvyklý. Jedním z důvodů, proč se to stává běžným uživatelům serveru SQL Server, je to, že SQL Server používá to, co se nazývá implicitní transakce. Implicitní transakce jsou ve výchozím nastavení vypnuty, což znamená, že SQL Server neočekává, že na konci každého příkazu vydáte příkaz COMMIT TRANSACTION. Ve skutečnosti je každý příkaz potvrzen automaticky. To je pohodlné a pomáhá to předejít situacím, kdy relace, které se teprve mají provést, skončí uzamčením zdrojů a ovlivněním výkonu. Brent Ozar uvádí více podrobností o dopadech na výkon IMPLICITNÍCH TRANSAKCÍ =ON.
Nicméně malou nevýhodou této konfigurace (IMPLICITNÍ TRANSAKCE =VYPNUTO) je, že uživatelé nemají příležitost přehodnotit prohlášení a vydat ROLLBACK, což je v Oracle velmi běžné. Obr. 1 ukazuje možnosti dotazu ANSI dostupné v SQL Server Management Studio.
>Obr. 1 Výchozí ANSI v SQL Server Management Studio
Použití implicitních transakcí
V podstatě problém, se kterým se potýkáme v této výchozí konfiguraci nebo v našem nejžádanějším klientském nástroji, je ten, že po provedení příkazu SQL nelze vrátit zpět. Můžeme to obejít tím, že v naší relaci povolíme IMPLICITNÍ TRANSAKCE. To nám dá příležitost ROLLBACK transakce, pokud to budeme potřebovat. Obr. 2 a obr. 4 nám ukazují, že toto nastavení můžeme mít zapnuté pouze pro jednu relaci, i když to nezbavuje riziko, že uživatelská relace zablokuje ostatní, pokud nebude vydán ROLLBACK nebo COMMIT.
>Obr. 2 IMPLICITNÍ TRANSAKCE ZAPNUTY v jedné relaci
-- Listing 1: UPDATE Table TAB2 with IMPLICIT_TRANSACTIONS ON SET IMPLICIT_TRANSACTIONS ON DECLARE @IMPLICIT_TRANSACTIONS VARCHAR(3) = 'OFF'; IF ( (2 & @@OPTIONS) = 2 ) SET @IMPLICIT_TRANSACTIONS = 'ON'; SELECT @IMPLICIT_TRANSACTIONS AS IMPLICIT_TRANSACTIONS; USE KTrain GO SELECT * FROM Tab2; GO UPDATE TAB2 SET countryCode='SA' -- WHERE fname='Joyce'; GO SELECT * FROM Tab2; GO
Obr. 3 Všechny řádky aktualizovány
Abychom ilustrovali zde popsané zástupné řešení, podívejme se na kód SQL ve výpisu 1. Předpokládejme, že běžný uživatel SQL Server, mladší vývojář, dostal sadu skriptů, které se mají spustit za určitých podmínek . Ve skriptu byla klauzule WHERE zakomentována, protože se očekává, že při každém spuštění tohoto skriptu by měli změnit predikát. Samozřejmě se jedná o jednoduchý případ použití a riziko lze řešit mnoha způsoby, ale my jen chceme ukázat možnost provedení ROLLBACK.
Připomeňme, že jsme již zapnuli IMPLICITNÍ TRANSAKCI, takže když provedeme tento příkaz, SQL Server bude očekávat, že transakci COMMIT nebo ROLLBACK budeme. Záměrem vývojáře je aktualizovat kód země Joyce Afam do „SA“, protože emigrovala do Jižní Afriky. Obr. 3 nám ukazuje, že vývojář při pokusu o to omylem aktualizoval všechny řádky s hodnotou SA jako countryCode . Všimnou si toho a vydají ROLLBACK.
>Obr. 4 Vydání ROLLBACK
>Obr. 5 IMPLICITNÍCH TRANSAKCÍ ZAPNUTO v jiné relaci
V další relaci, ve které jsme nezapnuli IMPLICITNÍ TRANSAKCE, jsme však zjistili, že se vývojář ze své chyby nedokáže zotavit. V tomto případě nemohou úspěšně vydat ROLLBACK. Obnova by pak vyžadovala obnovu dat.
>Obr. 6 ROLLBACK není možný bez ZAPNUTÝCH IMPLICITNÍCH TRANSAKCÍ
Používání explicitních transakcí
Dalším přístupem k dosažení stejného účinku je uzavření DML do transakce výslovným uvedením BEGIN TRAN. Opět je velmi důležité dokončit transakci – buď pomocí COMMIT nebo ROLLBACK. V souvislosti s touto diskuzí vydáváme ROLLBACK, protože si uvědomujeme, že v kódu je chyba.
-- Listing 2: UPDATE Table TAB2 with Explicit Transaction BEGIN TRAN GO USE KTrain GO SELECT * FROM Tab2; GO UPDATE TAB2 SET countryCode='GH' -- WHERE fname='Joyce'; GO SELECT * FROM Tab2; GO ROLLBACK; SELECT * FROM Tab2; GO - Listing 3: Corrected UPDATE Statement BEGIN TRAN GO USE KTrain GO SELECT * FROM Tab2; GO UPDATE TAB2 SET countryCode='SA' WHERE fname='Joyce'; GO SELECT * FROM Tab2; GO
Závěr
V tomto článku jsme se stručně dotkli dobrého řešení pro vytváření příležitostí pro ROLLBACK a tím zmírnění chyb uživatelů vyplývajících z nesprávného DML. Zdůraznili jsme také klíčové riziko tohoto přístupu, kterým je neúmyslné zablokování. DBA může zahájit vyšetřování možné přítomnosti tohoto rizika dotazem na sys.dm_tran_session_transactions, sys.dm_tran_locks a podobné objekty dynamické správy.
Odkazy
Oprava ztráty dat pomocí Log Shipping se zpožděným obnovením
Nastavení implicitních transakcí
Nastavit implicitní transakce jako špatný nápad
DMV pro transakce