Read uncommitted je nejslabší ze čtyř úrovní izolace transakcí definovaných ve standardu SQL (a ze šesti implementovaných v SQL Server). Umožňuje všechny tři takzvané "souběžné jevy", špinavé čtení , neopakovatelné čtení a přízraky:
Většina databázových lidí si je těchto jevů alespoň rámcově vědoma, ale ne každý si uvědomuje, že plně nepopisují nabízené záruky izolace; ani intuitivně nepopisují různé chování, které lze očekávat v konkrétní implementaci, jako je SQL Server. Více o tom později.
Izolace transakcí – „já“ v ACID
Každý příkaz SQL se provádí v rámci transakce (explicitní, implicitní nebo automatické potvrzení). Každá transakce má přidruženou úroveň izolace, která určuje, jak je izolovaná od účinků jiných souběžných transakcí. Tento poněkud technický koncept má důležité důsledky pro způsob provádění dotazů a kvalitu výsledků, které produkují.
Zvažte jednoduchý dotaz, který počítá všechny řádky v tabulce. Pokud by bylo možné tento dotaz provést okamžitě (nebo s nulovými souběžnými úpravami dat), mohla by existovat pouze jedna správná odpověď:počet řádků fyzicky přítomných v tabulce v daném okamžiku. Ve skutečnosti bude provedení dotazu trvat určitou dobu a výsledek bude záviset na tom, s kolika řádky se prováděcí modul skutečně setká, když prochází jakoukoli fyzickou strukturou zvolenou pro přístup k datům.
Pokud jsou řádky přidávány do tabulky (nebo z ní odstraněny) souběžnými transakcemi, zatímco operace počítání probíhá, mohou být získány různé výsledky v závislosti na tom, zda transakce počítání řádků narazí na všechny, některé nebo žádné z těchto souběžných změn – což zase závisí na úrovni izolace transakce počítání řádků.
V závislosti na úrovni izolace, fyzických detailech a načasování souběžných operací by naše počítací transakce mohla dokonce přinést výsledek, který nikdy nebyl skutečným odrazem potvrzeného stavu tabulky v žádném okamžiku během transakce.
Příklad
Zvažte transakci počítání řádků, která začíná v čase T1 a prohledává tabulku od začátku do konce (pro účely argumentu v pořadí klíčů seskupeného indexu). V tu chvíli je v tabulce 100 potvrzených řádků. O nějaký čas později (v čase T2) naše počítací transakce narazila na 50 těchto řádků. Ve stejném okamžiku souběžná transakce vloží do tabulky dva řádky a provede potvrzení o chvíli později v čase T3 (před ukončením počítací transakce). Jeden z vložených řádků náhodou spadá do poloviny seskupené indexové struktury, kterou naše počítací transakce již zpracovala, zatímco druhý vložený řádek leží v nezapočítané části.
Po dokončení transakce počítání řádků vykáže v tomto scénáři 101 řádků; 100 řádků zpočátku v tabulce plus jeden vložený řádek, který byl zjištěn během skenování. Tento výsledek je v rozporu s potvrzenou historií tabulky:v čase T1 a T2 bylo 100 potvrzených řádků, v čase T3 pak 102 potvrzených řádků. Nikdy nebyla doba, kdy by bylo 101 potvrzených řádků.
Překvapivá věc (možná v závislosti na tom, jak hluboce jste o těchto věcech dříve přemýšleli), je, že tento výsledek je možný na výchozí (uzamykací) úrovni izolace potvrzeného čtení a dokonce i při izolaci opakovatelného čtení. U obou těchto úrovní izolace je zaručeno, že budou číst pouze potvrzená data, přesto jsme získali výsledek, který nepředstavuje žádný potvrzený stav databáze!
Analýza
Jediná úroveň izolace transakcí, která poskytuje úplnou izolaci od souběžných efektů, je serializovatelná. Implementace úrovně izolace serializovatelného serveru SQL Server znamená, že transakce uvidí nejnovější potvrzená data od okamžiku, kdy byla data poprvé uzamčena pro přístup. Navíc je zaručeno, že sada dat, se kterými se setká v serializovatelné izolaci, nezmění své členství před ukončením transakce.
Příklad počítání řádků zdůrazňuje základní aspekt teorie databáze:musíme mít jasno v tom, co znamená „správný“ výsledek pro databázi, která prochází souběžnými úpravami, a musíme porozumět kompromisům, kterých se při výběru izolace dopouštíme. úroveň nižší než serializovatelná.
Pokud potřebujeme časový pohled na potvrzený stav databáze, měli bychom použít izolaci snímku (pro záruky na úrovni transakcí) nebo číst izolaci potvrzeného snímku (pro záruky na úrovni příkazů). Všimněte si však, že pohled v určitém okamžiku znamená, že nemusíme nutně pracovat na aktuálním potvrzeném stavu databáze; ve skutečnosti můžeme používat zastaralé informace. Na druhou stranu, pokud jsme spokojeni s výsledky založenými pouze na potvrzených datech (i když možná z různých časových bodů), mohli bychom se rozhodnout zůstat u výchozí úrovně izolace zamykání pro čtení.
Abychom si byli jisti, že výsledky (a rozhodování!) budou založeny na nejnovější sadě potvrzených dat, pro nějakou sériovou historii operací proti databázi bychom potřebovali serializovatelnou izolaci transakcí. Tato možnost je samozřejmě obvykle nejdražší z hlediska využití zdrojů a snížené souběžnosti (včetně zvýšeného rizika uváznutí).
V příkladu počítání řádků by obě úrovně izolace snímku (SI a RCSI) poskytly výsledek 100 řádků, což představuje počet potvrzených řádků na začátku příkazu (a v tomto případě transakce). Spuštění dotazu při uzamčení potvrzeného čtení nebo izolace opakovatelného čtení by mohlo mít za následek 100, 101 nebo 102 řádků – v závislosti na načasování, granularitě zámku, poloze vložení řádku a zvolené metodě fyzického přístupu. Při serializovatelné izolaci by výsledkem bylo 100 nebo 102 řádků v závislosti na tom, která ze dvou souběžných transakcí se považuje za provedenou jako první.
Jak špatné je čtení bez závazku?
Po zavedení izolace nepotvrzeného čtení jako nejslabší z dostupných úrovní izolace byste měli očekávat, že nabídne ještě nižší záruky izolace než zamykání potvrzeného čtení (další nejvyšší úroveň izolace). Opravdu ano; ale otázka zní:o kolik horší než zamykání izolace potvrzené čtením je to?
Abychom začali se správným kontextem, zde je seznam hlavních souběžných efektů, které lze zaznamenat pod výchozí úrovní izolace zamykání pro čtení potvrzené SQL Server:
- Chybí potvrzené řádky
- Řádky byly zjištěny několikrát
- Různé verze stejného řádku nalezené v jediném plánu příkazu/dotazu
- Potvrzená data sloupců z různých časových okamžiků ve stejném řádku (příklad)
Všechny tyto souběžné efekty jsou způsobeny zamykací implementací funkce pro čtení potvrzené pouze s velmi krátkodobými sdílenými zámky při čtení dat. Úroveň izolace nepotvrzeného čtení jde ještě o krok dále, protože vůbec nebere sdílené zámky, což má za následek další možnost „špinavého čtení“.
Nečisté čtení
Pro rychlé připomenutí, „nečisté čtení“ se týká čtení dat, která jsou změněna jinou souběžnou transakcí (kde „změna“ zahrnuje operace vložení, aktualizace, odstranění a sloučení). Jinak řečeno, nečisté čtení nastane, když transakce načte data, která jiná transakce upravila, předtím, než modifikující transakce tyto změny potvrdí nebo přeruší.
Výhody a nevýhody
Primárními výhodami izolace bez závazku čtení je snížený potenciál pro blokování a zablokování kvůli nekompatibilním zámkům (včetně zbytečného blokování kvůli eskalaci zámků) a možná zvýšený výkon (tím, že se vyhnete nutnosti získávat a uvolňovat sdílené zámky).
Nejzjevnější potenciální nevýhodou izolace čtení bez potvrzení je (jak název napovídá), že můžeme číst nepřijatá data (dokonce i data, která nejsou nikdy potvrzeno, v případě vrácení transakce). V databázi, kde jsou rollbacky relativně vzácné, může být otázka čtení nepotvrzených dat vnímána jako pouhý problém s načasováním, protože dotyčná data budou v určité fázi jistě potvrzena a pravděpodobně poměrně brzy. Již jsme viděli nekonzistence související s načasováním v příkladu počítání řádků (který fungoval na vyšší úrovni izolace), takže by se dalo dobře pochybovat o tom, jak velký problém je číst data "příliš brzy."
Odpověď zjevně závisí na místních prioritách a kontextu, ale informované rozhodnutí použít bezzávaznou izolaci pro čtení se jistě zdá možné. Je však o čem přemýšlet. Implementace SQL Server úrovně izolace bez závazku čtení zahrnuje některé jemné chování, o kterém si musíme být vědomi, než uděláme tuto "informovanou volbu."
Skenování alokačních příkazů
Použití číst nepotvrzené izolace bere SQL Server jako signál, že jsme připraveni přijmout nesrovnalosti, které by mohly nastat v důsledku skenování nařízeného alokací.
Úložný modul může obvykle zvolit skenování nařízenou alokací, pouze pokud je zaručeno, že se základní data nezmění během kontroly (protože například databáze je pouze pro čtení nebo byla zadána nápověda k uzamčení tabulky). Když se však používá izolace bez potvrzení čtení, úložný modul může stále zvolit skenování podle pořadí alokace, i když mohou být podkladová data změněna souběžnými transakcemi.
Za těchto okolností může skenování podle pořadí přidělení zcela vynechat některá potvrzená data nebo narazit na jiná potvrzená data více než jednou. Důraz je kladen na chybějící nebo dvojí započtení zavázané data (nikoli čtení nepotvrzených dat), takže se nejedná o „špinavé čtení“ jako takové. Toto rozhodnutí o návrhu (umožnit skenování nařízená alokací v rámci izolace bez závazku čtení) je některými lidmi vnímáno jako poněkud kontroverzní.
Jako upozornění by mi mělo být jasné, že obecnější riziko chybějících nebo dvojích započtení potvrzených řádků se neomezuje na čtení neregistrované izolace. Je jistě možné vidět podobné efekty pod uzamčením čtení potvrzeného a opakovatelného čtení (jak jsme viděli dříve), ale k tomu dochází prostřednictvím jiného mechanismu. Chybějící potvrzené řádky nebo se s nimi opakovaně setkáváme kvůli skenování na základě pořadí změn dat je specifický pro použití izolace bez závazku čtení.
Čtení "poškozených" dat
Výsledky, které se zdají odporovat logice (a dokonce kontrolovat omezení!), jsou možné při zamykání izolace potvrzené čtením (několik příkladů opět viz tento článek Craiga Freedmana). Abych to shrnul, jde o to, že uzamčením čtení potvrzeného lze vidět potvrzená data z různých časových okamžiků – dokonce i pro jeden řádek, pokud například plán dotazů používá techniky, jako je průnik indexu.
Tyto výsledky mohou být neočekávané, ale jsou zcela v souladu se zárukou pouze čtení potvrzených dat. Neexistuje žádný únik ze skutečnosti, že vyšší záruky konzistence dat vyžadují vyšší úrovně izolace.
Tyto příklady mohou být dokonce docela šokující, pokud jste je ještě neviděli. Stejné výsledky jsou samozřejmě možné při izolaci čtení bez potvrzení, ale povolení nečistých čtení přidává další rozměr:výsledky mohou zahrnovat potvrzená a nepotvrzená data z různých časových bodů, dokonce i pro stejný řádek.
Jdeme ještě dále, je dokonce možné, že přečtená transakce bez potvrzení přečte hodnotu jednoho sloupce ve smíšeném stavu potvrzených a nepotvrzených dat. K tomu může dojít při čtení hodnoty LOB (například xml nebo kteréhokoli z typů 'max'), pokud je hodnota uložena na více datových stránkách. Nepotvrzené čtení může narazit na potvrzená nebo nepotvrzená data z různých časových okamžiků na různých stránkách, což má za následek konečnou hodnotu jednoho sloupce, která je směsí hodnot!
Chcete-li vzít příklad, zvažte jeden sloupec varchar(max), který zpočátku obsahuje 10 000 znaků 'x'. Souběžná transakce aktualizuje tuto hodnotu na 10 000 znaků 'y'. Čtení nepotvrzené transakce může číst znaky 'x' z jedné stránky LOB a znaky 'y' z jiné, což vede ke konečné načtené hodnotě obsahující směs znaků 'x' a 'y'. Je těžké tvrdit, že to nepředstavuje čtení „poškozených“ dat.
Ukázka
Vytvořte seskupenou tabulku s jedním řádkem dat LOB:
CREATE TABLE dbo.Test ( RowID integer PRIMARY KEY, LOB varchar(max) NOT NULL, ); INSERT dbo.Test (RowID, LOB) VALUES (1, REPLICATE(CONVERT(varchar(max), 'X'), 16100));
V samostatné relaci spusťte následující skript ke čtení hodnoty LOB při izolaci čtení bez potvrzení:
-- Run this in session 2 SET NOCOUNT ON; DECLARE @ValueRead varchar(max) = '', @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100), @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100); WHILE 1 = 1 BEGIN SELECT @ValueRead = T.LOB FROM dbo.Test AS T WITH (READUNCOMMITTED) WHERE T.RowID = 1; IF @ValueRead NOT IN (@AllXs, @AllYs) BEGIN PRINT LEFT(@ValueRead, 8000); PRINT RIGHT(@ValueRead, 8000); BREAK; END END;
V první relaci spusťte tento skript a zapište do sloupce LOB střídavé hodnoty:
-- Run this in session 1 SET NOCOUNT ON; DECLARE @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100), @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100); WHILE 1 = 1 BEGIN UPDATE dbo.Test SET LOB = @AllYs WHERE RowID = 1; UPDATE dbo.Test SET LOB = @AllXs WHERE RowID = 1; END;
Po krátké době se skript v relaci 2 ukončí a načte smíšený stav hodnoty LOB, například:
Tento konkrétní problém se omezuje na čtení hodnot sloupců LOB, které jsou rozloženy na více stránkách, nikoli z důvodu jakýchkoli záruk poskytovaných úrovní izolace, ale proto, že SQL Server náhodou používá západky na úrovni stránky k zajištění fyzické integrity. Vedlejším efektem tohoto detailu implementace je to, že zabraňuje takovému "poškozenému" čtení dat, pokud se data pro jednu operaci čtení náhodou nacházejí na jediné stránce.
V závislosti na verzi serveru SQL, kterou máte, pokud jsou pro sloupec xml načtena data „smíšený stav“, zobrazí se buď chyba vyplývající z pravděpodobně chybně vytvořeného výsledku xml, žádná chyba nebo specifická chyba 601 bez potvrzení. , "nemohlo pokračovat skenování s NOLOCK kvůli pohybu dat." Čtení dat se smíšeným stavem pro jiné typy LOB obecně nevede k chybové zprávě; náročná aplikace nebo dotaz nemá žádný způsob, jak zjistit, že právě zažil nejhorší druh špinavého čtení. Aby byla analýza dokončena, řádek se smíšeným stavem bez LOB přečtený jako výsledek průsečíku indexu není nikdy hlášen jako chyba.
Zpráva zní, že pokud použijete izolaci pro čtení bez potvrzení, souhlasíte s tím, že špinavá čtení zahrnují možnost čtení „poškozených“ smíšených hodnot LOB.
Nápověda NOLOCK
Předpokládám, že žádná diskuse o úrovni izolace bez závazku čtení by nebyla úplná, aniž by se alespoň nezmínila tato (velmi nadužívaná a nepochopená) tabulková nápověda. Samotná nápověda je pouze synonymem nápovědy tabulky READUNCOMMITTED. Plní přesně stejnou funkci:k objektu, na který je aplikován, se přistupuje pomocí sémantiky nepotvrzené izolace čtení (i když existuje výjimka).
Pokud jde o název "NOLOCK", znamená to jednoduše, že při čtení dat nejsou přijímány žádné sdílené zámky . Ostatní zámky (stabilita schématu, výhradní zámky pro úpravu dat atd.) jsou stále brány jako normální.
Obecně řečeno, rady NOLOCK by měly být asi tak běžné jako jiné rady pro tabulky úrovně izolace pro jednotlivé objekty, jako je SERIALIZABLE a READCOMMITTEDLOCK. To znamená:není vůbec běžné a používá se pouze tam, kde neexistuje dobrá alternativa, má dobře definovaný účel a je úplný pochopení důsledků.
Jedním z příkladů legitimního použití NOLOCK (nebo READUNCOMMITTED) je přístup k DMV nebo jiným systémovým pohledům, kde vyšší úroveň izolace může způsobit nežádoucí spory o neuživatelské datové struktury. Dalším příkladem okrajového případu může být situace, kdy dotaz potřebuje získat přístup k významné části velké tabulky, u které je zaručeno, že během provádění naznačeného dotazu nikdy nedojde ke změnám dat. Musel by existovat dobrý důvod, proč místo toho nepoužívat izolaci snímku nebo číst potvrzený snímek a očekávané zvýšení výkonu by bylo nutné otestovat, ověřit a porovnat, řekněme, s použitím jediné nápovědy k uzamčení sdílené tabulky.
Nejméně žádoucí použití NOLOCK je to, které je bohužel nejběžnější:jeho použití na každý objekt v dotazu jako jakýsi rychlejší magický spínač. S nejlepší vůlí na světě prostě neexistuje lepší způsob, jak zajistit, aby kód SQL Server vypadal rozhodně amatérsky. Pokud oprávněně potřebujete pro dotaz, blok kódu nebo modul číst nepotvrzenou izolaci, je pravděpodobně lepší vhodně nastavit úroveň izolace relace a přidat komentáře k ospravedlnění akce.
Poslední myšlenky
Číst bez závazku je legitimní volbou pro úroveň izolace transakcí, ale musí to být informovaná volba. Připomínáme, že zde jsou některé z jevů souběžnosti, které jsou možné v rámci výchozího zamykání izolace pro čtení potvrzeného serveru SQL Server:
- Chybí dříve potvrzené řádky
- Potvrzené řádky byly zjištěny několikrát
- V jediném plánu příkazu/dotazu byly zjištěny různé potvrzené verze stejného řádku
- Potvrzené údaje z různých časových okamžiků ve stejném řádku (ale v různých sloupcích)
- Čtení potvrzených dat, která se zdají být v rozporu s povolenými a zaškrtnutými omezeními
V závislosti na vašem úhlu pohledu to může být docela šokující seznam možných nekonzistencí pro výchozí úroveň izolace. K tomuto seznamu si přečtěte uncommitted isolation added:
- Nečisté čtení (setkání s daty, která ještě nebyla a možná nikdy nebudou potvrzena)
- Řádky obsahující směs potvrzených a nepotvrzených dat
- Zmeškané/duplicitní řádky kvůli skenům nařízeným alokací
- Smíšené ("poškozené") jednotlivé (jednosloupcové) hodnoty LOB
- Chyba 601 – „nemohlo pokračovat skenování pomocí NOLOCK kvůli pohybu dat“ (příklad).
Pokud se vaše primární transakční obavy týkají vedlejších efektů zamykání izolace s potvrzením čtení – blokování, režie zamykání, snížená souběžnost kvůli eskalaci uzamčení atd. – možná by vám lépe posloužila úroveň izolace verzování řádků, jako je izolace snímku potvrzeného čtení (RCSI) nebo snapshot izolace (SI). Ty však nejsou zdarma a zejména aktualizace pod RCSI mají určité protiintuitivní chování.
Pro scénáře, které požadují nejvyšší úrovně záruk konzistence, zůstává serializovatelnost jedinou bezpečnou volbou. Pro výkonově kritické operace s daty pouze pro čtení (například velké databáze, které jsou efektivně jen pro čtení mezi okny ETL), může být dobrou volbou také explicitní nastavení databáze na READ_ONLY (sdílené zámky se nepřebírají, když je databáze pouze pro čtení a nehrozí zde žádné riziko nekonzistence).
Bude také relativně malý počet aplikací, pro které je izolace bez závazku čtení tou správnou volbou. Tyto aplikace musí být spokojené s přibližnými výsledky a možností občasných nekonzistentních, zjevně neplatných (z hlediska omezení) nebo „pravděpodobně poškozených“ dat. Pokud se data mění relativně zřídka, riziko těchto nekonzistencí je také odpovídajícím způsobem nižší.
[ Viz rejstřík pro celou sérii ]