<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Rollback &#187; tranzakció</title>
	<atom:link href="http://blog.rollback.hu/tag/tranzakcio/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.rollback.hu</link>
	<description>SQL, üzemeltetés kicsiknek és nagyoknak.</description>
	<lastBuildDate>Thu, 17 Nov 2011 16:38:59 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.6</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Az OUTPUT clause</title>
		<link>http://blog.rollback.hu/2010/10/az-output-clause/</link>
		<comments>http://blog.rollback.hu/2010/10/az-output-clause/#comments</comments>
		<pubDate>Wed, 06 Oct 2010 22:32:51 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Magyar]]></category>
		<category><![CDATA[konkurrencia]]></category>
		<category><![CDATA[script]]></category>
		<category><![CDATA[sql2008]]></category>
		<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[tranzakció]]></category>

		<guid isPermaLink="false">http://blog.rollback.hu/?p=284</guid>
		<description><![CDATA[Ideje, hogy valami szakmait is írjak, mert kezd uncsi lenni a blog. Szóval van egy szép hosszú listám arról, hogy mi mindent nem mondtam el a Millenárisban tartott beszédemben, és ebből mazsolázgatok. Kezdjük is el!
Az első egy kicsi, de hasznos apróság: az OUTPUT clause vagy ahogy Serény tanár úr írná, a klóz. Mire jó? Tipikus [...]]]></description>
			<content:encoded><![CDATA[<p>Ideje, hogy valami szakmait is írjak, mert kezd uncsi lenni a blog. Szóval van egy szép hosszú listám arról, hogy mi mindent nem mondtam el a Millenárisban tartott beszédemben, és ebből mazsolázgatok. Kezdjük is el!</p>
<p>Az első egy kicsi, de hasznos apróság: az OUTPUT clause vagy ahogy Serény tanár úr írná, a klóz. Mire jó? Tipikus jelenet, hogy kell update-elnem rekordokat egy OLTP adatbázisban, azaz az adat változik alattam, de szeretném tudni, hogy pontosan melyeket update-eltem meg. Erre a gyenge megoldás a </p>
<pre class="brush: sql;">
select primary_key into #temptabla from tabla
where status = 'NEW'

update tabla where primary_key in (select primary_key from #temptabla)
set status = 'ACTIVE'

drop table #temptabla
</pre>
<p>Ez egy egész jó megközelítés, két feltétel teljesülése esetén:</p>
<ol>
<li>Nulla konkurrencia lehetséges, azaz pl. nem akar egyszerre két alkalmazásszerver dolgozni az adatbáziban, ekkor ugyanis ők lelkesen dolgozzák fel a rekordokat &#8211; egyszerre. Ezt nem részletezném, hogy miért gáz.</li>
<li>2008-at vagy kevesebbet írunk. Azóta ugyanis van OUTPUT clause.</li>
</ol>
<p>A dolog roppant egyszerű: bele van dugva egy belső trigger gyakorlatilag, ami a következőt tudja: ha beletömjük az OUTPUT-ot az INSERT/UPDATE/DELETE utasításba, akkor lesz inserted és/vagy deleted táblánk, és lehet kiszipkázni az adatokat, akár csak kiechózni, akár betenni táblaváltozókba. Az OUTPUT helye a WHERE feltétel előtt van még, erre figyeljetek. Valahogy így:</p>
<pre class="brush: sql;">
UPDATE tabla
SET status = 'ACTIVE'
OUTPUT inserted.primary_key
WHERE status = 'NEW'
</pre>
<p>Valljuk be, hogy jobban néz ki. Ha el akarjuk menteni az outputot későbbi reszeléshez vagy csak nyakonvágni a teljesítményt, akkor pedig a fenti példa így néz ki:</p>
<pre class="brush: sql;">
DECLARE @tablacska table (primary_key int);
UPDATE tabla
SET status = 'ACTIVE'
OUTPUT inserted.primary_key INTO @tablacska
WHERE status = 'NEW';
SELECT primary_key FROM @tablacska;
</pre>
<p>Ez egyébként fokozható, például ha már így előjött a konkurrencia, mint probléma, kiválóan megoldható a sok alkalmazás egy táblát túr probléma ezzel meg egy kis locking hinteléssel:</p>
<pre class="brush: sql;">
UPDATE tabla WITH (READPAST)
SET status = 'ACTIVE'
OUTPUT inserted.*
WHERE status = 'NEW'
</pre>
<p>Így ha több szerver dolgoz fel egy queue-t, hogy szépen mondjam, nem kell nagyon foglalkozniuk a másikkal. Egy konkrét szerver odamegy, kivesz annyi rekordot, amennyit akar (mert lehet TOP-ot is mondani az update-ben), ha valaki más is éppen update-el, akkor az általa lockolt rekordokat átugorja, és keres olyat, ami nincs lockolva. Ez a READPAST ajándéka: átugorja a lockolt rekordokat, így nem akadnak fenn egymáson a párhuzamos processzek.</p>
<p>(disclaimer: épp nincs SQL szerverem, ezért a példákat fejből írtam, ha nem működnek, bocsi, kijavítom :)</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rollback.hu/2010/10/az-output-clause/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Folyamatos sorszámosztás, avagy szekvencia MSSQL módra</title>
		<link>http://blog.rollback.hu/2010/01/folyamatos-sorszamosztas-avagy-szekvencia-mssql-modra/</link>
		<comments>http://blog.rollback.hu/2010/01/folyamatos-sorszamosztas-avagy-szekvencia-mssql-modra/#comments</comments>
		<pubDate>Tue, 12 Jan 2010 23:56:40 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Magyar]]></category>
		<category><![CDATA[más tolla]]></category>
		<category><![CDATA[script]]></category>
		<category><![CDATA[T-SQL]]></category>
		<category><![CDATA[tranzakció]]></category>

		<guid isPermaLink="false">http://blog.rollback.hu/?p=163</guid>
		<description><![CDATA[Itzik Ben-Gan egyike a nagy SQL koponyáknak napjainkban. Sajnos blogot nem ír, szerencsére könyvet igen. Alapvetően T-SQL programozással foglalkozna, de a jó T-SQL ugye hatékony is, tehát jön a Query Optimizer komponense az SQL Servernek, aztán a Query Processor, meg persze egy kicsit a tárolást sem árt ismerni&#8230; szóval ezermester. Szereti a fejtörőket, és szerintem [...]]]></description>
			<content:encoded><![CDATA[<p>Itzik Ben-Gan egyike a nagy SQL koponyáknak napjainkban. Sajnos blogot nem ír, szerencsére könyvet igen. Alapvetően T-SQL programozással foglalkozna, de a jó T-SQL ugye hatékony is, tehát jön a Query Optimizer komponense az SQL Servernek, aztán a Query Processor, meg persze egy kicsit a tárolást sem árt ismerni&#8230; szóval ezermester. Szereti a fejtörőket, és szerintem ezzel összefüggésben áll az, hogy zseniális scripteket ír.</p>
<p>Van egy elég általános probléma alkalmazásfejlesztésben: folyamatos sorszámozás az adatbázisban &#8211; a klasszikus példa a számlaszám, amiben nem lehetnek lyukak, mert attól a könyvelő is meg az APEH is ideges. Erre van többféle megoldás, némelyik jó, némelyik nem. Nézzük előbb a rosszakat:</p>
<ol>
<li><del><strong>Használjunk identity oszlopot!</strong></del> Ez minden sikertelen beszúrásnál is növelni fogja a sorszámot, és lukak maradnak utána.</li>
<li> <del><strong>Kérdezzük le az utolsó kiadott sorszámot, és tároljuk el az eggyel nagyobbat.</strong></del> Ez csábítóan egyszerű, és speciális esetekben akár jó is lehet, a probléma a konkurrencia: ha ketten egyszerre kérdezik le a legutolsó sorszámot, ugyanazt fogják kiosztani következőnek.</li>
</ol>
<p>És nézzünk egy jót: Tároljuk el a számlarekordot, egyelőre számlaszám nélkül, egy másik egyedi azonosítóval (ez lehet akár egy identity oszlop is), és tartsunk fenn egy táblát, aminek egyetlen oszlopa identity. Minden számlarekord eltárolás után húzzunk egy sorszámot (azaz szúrjunk be egy rekordot), és azt tegyük el számlaszámként. Mivel itt nem lehet sikertelen beszúrás, a sorszámozás folyamatos lesz. De mit csinálunk a nagyra nőtt sorszámgeneráló táblával? a TRUNCATE TABLE nem jó, mert az ugyan gyorsan üríti a táblát, de reseteli az identity értéket is, tehát megint az elejéről kezdjük osztani a számlákat. A DELETE technikailag jó, de sok rekord törlésénél esetleg lock eszkalációba futunk, aminek az lesz a vége, hogy senki nem fog új számlát készíteni egy darabig.</p>
<p>A problémára gyógyír Itzik egy kis mintascriptje:</p>
<pre class="brush: sql;">
USE tempdb;
CREATE TABLE dbo.AsyncSeq(val INT IDENTITY);
GO
CREATE PROC dbo.usp_AsyncSeq
 @val AS INT OUTPUT
AS
BEGIN TRAN
 SAVE TRAN S1;
 INSERT INTO dbo.AsyncSeq DEFAULT VALUES;
 SET @val = SCOPE_IDENTITY()
 ROLLBACK TRAN S1;
COMMIT TRAN
GO
</pre>
<p>Ez a dbo.usp_AsyncSeq tárolt eljárás tulajdonképpen az Oracle szekvencia implementációja. Fog egy táblát, amibe mindig beszúr, de nem menti el a beszúrást igazából, csak lekéri az identity értéket. Így a tábla mindig üres, és a karbantartása meg van oldva gyárilag. Használata kb. a következő:</p>
<pre class="brush: sql;">

USE tempdb;
-- készítünk egy számla táblát
CREATE TABLE dbo.Szamla (
a int identity,
b varchar(10) PRIMARY KEY,
--csak azért kell ez a primary key, mert így a legkönnyebb sikertelen beszúrásokat szimulálni később
szamlaszam int)
GO

-- meg egy tárolt eljárást
CREATE PROCEDURE dbo.usp_UjSzamla
@b varchar(10)
AS
-- ha nem sikerül a számlarekord elmentése, ez a beállítás megakadályozza, hogy továbbfusson a script, és feleslegesen húzzon sorszámot
SET XACT_ABORT ON
declare @a int, @szla int
INSERT INTO dbo.Szamla(b) VALUES(@b)
SELECT @a = SCOPE_IDENTITY()
-- itt hívjuk meg a sorszámosztót, ha sikeresen elmentettük a számlát
EXEC dbo.usp_AsyncSeq @val = @szla OUTPUT;
UPDATE dbo.Szamla SET szamlaszam = @szla WHERE a = @a;
-- kikapcsoljuk a szigorúságot
SET XACT_ABORT OFF
GO

--és kipróbáljuk:
EXEC dbo.usp_UjSzamla 'PROBA'
EXEC dbo.usp_UjSzamla 'PROBA'
EXEC dbo.usp_UjSzamla 'PROBA2'

select * from dbo.Szamla
select * from dbo.AsyncSeq
</pre>
<p>Az eredeti tárolt eljárásban megüthette az ember szemét a SAVE TRAN(SACTION) utasítás. Ez pont olyan, mint a SAVE GAME a játékokban: vissza lehet állni a mentett állapotra. Igen, egy tranzakción belül, tulajdonképpen részleges rollback, ami ugye nincs. De mégis van. Erről a témáról hamarosan lesz egy cikk a Technet portálon, egyelőre érjétek be ennyivel.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rollback.hu/2010/01/folyamatos-sorszamosztas-avagy-szekvencia-mssql-modra/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Tranzakciós log ürítése és nem ürítése</title>
		<link>http://blog.rollback.hu/2008/11/tranzakcios-log-uritese-es-nem-uritese/</link>
		<comments>http://blog.rollback.hu/2008/11/tranzakcios-log-uritese-es-nem-uritese/#comments</comments>
		<pubDate>Tue, 04 Nov 2008 21:25:24 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Magyar]]></category>
		<category><![CDATA[recovery model]]></category>
		<category><![CDATA[replikáció]]></category>
		<category><![CDATA[sql]]></category>
		<category><![CDATA[transaction log]]></category>
		<category><![CDATA[tranzakció]]></category>

		<guid isPermaLink="false">http://blog.rollback.hu/?p=31</guid>
		<description><![CDATA[   Mostanság SQL DBA-t keresek, és szakmai tesztekkel és interjúkkal is töltöm rengeteg szabadidőmet. Alapvetően megerősítve látom azon véleményemet, hogy az informatika vallásközeli tudomány, és sokan transzcendens vonásokat találnak a szerverek működésében, és babonás eszközökkel közelítik meg őket. Az egyik ilyen misztikus terület a tranzakciós log szerepe és használata.
   Mindenekelőtt szeretném [...]]]></description>
			<content:encoded><![CDATA[<p>   Mostanság <a href="http://blog.rollback.hu/2008/09/sql-dba-wanted/">SQL DBA-t keresek</a>, és szakmai tesztekkel és interjúkkal is töltöm rengeteg szabadidőmet. Alapvetően megerősítve látom azon véleményemet, hogy az informatika vallásközeli tudomány, és sokan transzcendens vonásokat találnak a szerverek működésében, és babonás eszközökkel közelítik meg őket. Az egyik ilyen misztikus terület a tranzakciós log szerepe és használata.<br />
   Mindenekelőtt szeretném leszögezni a következőt: <strong>a tranzakció teljesülését a diszkre fogja kiírni rögtön az SQL szerver</strong>, nem a memóriában írja a tranzakciós logot, mert annak ugye nem volna sok értelme, ha a commit tényét egy illékony tárban rögzítené. De ez részletkérdés pillanatnyi motivációm szempontjából, nézzük inkább meg, hogy mikor szabadítja fel az SQL a tranzakciós logot. Három feltételnek kell tejesülnie:</p>
<ol>
<li>Az adatbázis <strong>recovery modellje simple</strong> vagy (full és bulk-logged esetén) az adott naplórész <strong>le lett backupolva</strong>.</li>
<li>A tranzakciós log szóban forgó része <strong>nem tartalmaz aktív tranzakciót</strong>, azaz minden olyan tranzakció, ami a log írásakor aktív volt, már véget ért (ha rollback lett ,akkor a rollback is véget ért).</li>
<li>Az adatbázis nincs replikálva vagy ha igen, akkor a <strong>Log Reader Agent már elolvasta </strong>az adott részt</li>
</ol>
<p>   Ezeknek a feltételeknek egyszerre teljesülniük kell. Azaz ha leállítom a Log Reader Agentet, akkor elkezd nőni a tranlog az égig, hogy ne rontsa el a replikációt. Hasonlóan elkezd nőni a log, ha sikerült egy tranzakicónak nyitva maradnia az adatbázisban, és lassan az egész tranlog aktívvá válik miatta.<br />
   Tehát ha megnő a log, érdemes a fenti pontokat ellenőrizni, körülbelül ebben a sorrendben (recovery model/utolsó log backup; legrégebbi nyitott tranzakció; replikáció esetén LRA).<br />
   Ja, még egy utolsó: a TRUNCATE LOG parancsok margójára: ha valaki rendszeresen kiadja a truncate log parancsot, akkor gondolkozzon el azon, hogy miért van full recovery model beállítva, és gyorsan állítsa át simple-re. Köszi.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rollback.hu/2008/11/tranzakcios-log-uritese-es-nem-uritese/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Tranzakció izolációs szintek avagy konkurrencia MSSQL módra I.</title>
		<link>http://blog.rollback.hu/2008/07/tranzakcio-izolacios-szintek-avagy-konkurrencia-mssql-modra-i/</link>
		<comments>http://blog.rollback.hu/2008/07/tranzakcio-izolacios-szintek-avagy-konkurrencia-mssql-modra-i/#comments</comments>
		<pubDate>Sun, 06 Jul 2008 12:08:10 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Magyar]]></category>
		<category><![CDATA[konkurrencia]]></category>
		<category><![CDATA[mssql]]></category>
		<category><![CDATA[sql2005]]></category>
		<category><![CDATA[tranzakció]]></category>

		<guid isPermaLink="false">http://blog.rollback.hu/?p=13</guid>
		<description><![CDATA[Az egyik téma, amiröl nem tudtam beszélni a termékbejelentési konferencián, a &#8220;logikai&#8221; tuning, aminek egy kiváló útja a konkurrencia kezelésének megfelelö megválasztása. Ez a Microsoft SQL Server családban a transaction isolation level (tranzakció izolációs szint, hogy gyalázzuk a nyelvet) beállításával lehetséges. Ezzel a módszerrel azt határozzuk meg, hogy hogyan kezelje a szerver az egyidejű hozzáférési [...]]]></description>
			<content:encoded><![CDATA[<p>Az egyik téma, amiröl nem tudtam beszélni a termékbejelentési konferencián, a &#8220;logikai&#8221; tuning, aminek egy kiváló útja a konkurrencia kezelésének megfelelö megválasztása. Ez a Microsoft SQL Server családban a <strong>transaction isolation level</strong> (tranzakció izolációs szint, hogy gyalázzuk a nyelvet) beállításával lehetséges. Ezzel a módszerrel azt határozzuk meg, hogy hogyan kezelje a szerver az egyidejű hozzáférési kérelmeket ugyanahhoz az objektumhoz. Az objektum itt lehet tábla, extent, key range, lap, vagy akár egyetlen rekord is. Például mi történjen akkor, ha egy processz olvasni/módosítani/beszúrni akar egy olyan táblába(n), amit egy másik processz éppen ír/olvas/módosít. Helyes megválasztása esetén az alkalmazásunk egyszerűen jól fog működni, egy rossz döntéssel pedig feláldozhatjuk a teljesítményt vagy a logikai konzisztenciát. A transaction isolation level az SQL 2000-ben gyakorlatilag a lockok kezeléséről szól, aki szereti a perverziót, az ne használja őket, hanem írjon mindenhova locking hinteket (na jó, nem mindenhol lehet, de többnyire igen).<span id="more-13"></span><br />
   Miből lehet választani? Hát, az alapértelmezett érték szokás szerint a kellemes középszint, az esetek 99%-ára, ezt úgy nevezik, hogy READ COMMITTED. A read committed azt jelenti, hogy a tranzakcióink mindig csak olyan rekordokat olvasnak, amik ööö&#8230; kommittálva vannak (említettem már, hogy nem kéne erőltetni a magyarításokat az SQL-nél?), azaz nincs nyitott tranzakció, ami dolgozna rajtuk. Ez egy jó alapgondolat, a probléma ott kezdődik, amikor sokan akarnak olvasni meg írni valahova. És előáll az a hálás helyzet, hogy az olvasók várnak az írókra, hogy végre befejezzék az írást, legyen commit, és olvashassanak végre.  Ennél még fájdalmasabb tud lenni az, amikor az írók írnának, de nem tudnak, mert az olvasók egyfolytában olvasnak, és shared lockokat helyeznek el az olvasott adatokon a konzisztencia jegyében, az írás pedig ugye exkluzív lockot igényelne&#8230; Azaz a konkurrencia növekedésével a teljesítmény erősen hanyatlik. Ez az a tulajdonság, amiért sok informatikus szerette volna az MSSQL fejlesztőit tartós hőkezelésnek alávetni, és ráadásul igazuk is volt. Ebből a zsákutcából ugyanis egyetlen kiút volt: a READ UNCOMMITTED transaction level, ami viszont hordoz némi kockázatot magában. Ebben az esetben ugyanis a tranzakció olvas mindent, amit lát, nem lockol semmit, nem érdekli az, hogy a rekord még épp módosítás alatt van egy tranzakcióban, csak olvas. Emiatt a teljesítménye kiváló, konkurrenciában nála jobb nincs, viszont ő képes arra, hogy a következőket elkövesse:</p>
<ol>
<li>Egy rekord többszöri kiolvasása &#8211; ha egy rekordot módosítanak, és hátrébb kerül az indexfában, uncommitted szinten simán kétszer kiolvasható. </li>
<li>Egy rekord kihagyása &#8211; pont mint az előző, csak fordítva: ha előre kerül a rekord, és átugorja az olvasás aktuális pozícióját, sosem látjuk az eredményhalmazban.</li>
<li>Sosem létezett rekord olvasása &#8211; ha egy tranzakció beszúr egy rekordot, majd később rollback lesz a sorsa, akkor az a rekord ugyan sosem került be az adatbázisba, mi mégis kiolvashattuk, ha épp arra jártunk.</li>
<li>Létező rekord kihagyása &#8211; az előző fordítottja: ha egy tranzakció töröl egy rekordot, majd később rollback lesz a sorsa, akkor az a rekord ugyan sosem került ki az adatbázisból, mi mégsem láttuk az eredményhalmazban.</li>
</ol>
<p>Ezek után felmerül a kérdés, hogy miért volna valaki olyan állat, hogy használja? Hát, például mert őt ezek az apróságok nem zavarják. Például (úgy) tudja, hogy olyan információkat kérdez így le, amiket nem befolyásolnak a fenti esetek. Egyszerű példa: azt akarom tudni, hogy hány előfizetőm van éppen, és mindegy, hogy 15643 vagy 15641 lesz a végeredmény. És amiért az üzemeltető él-hal a read uncommitted szintért: amikor fut egy masszív update/insert, ami mondjuk tízmillió rekordot darál végig, akkor a read uncommitted tökéletesen alkalmas arra, hogy megnézzük, hol is tart a processzünk, hacsak nem lőttünk annyira a teljesítményre, hogy belockoltuk az egész táblát, mert az ellen nem véd. Én például gyakran használom erre, sikerrel.<br />
   Aki hatékonyságra vágyott, az a read committed és uncommitted párokkal játszhatott, már SQL 2000 óta, és azért gondolkodhatott el a fejlesztők nyílt lángnak való kitételén, mert esetleg kikacsintott a világba, és látta, hogy az Oracle hogyan kezeli ezt az érdekes kérdést. Ott <strong>row versioning</strong> (sorverziózás) volt (van), és nem kellett választani, hogy kétes adatokat olvasson az alkalmazás, vagy nem-sokkal-többfelhasználós legyen. Egyszerűen annyit tett az adatbázis motor, hogy a módosuló soroknak az eredeti verzióját eltárolta, és aki ki akarta olvasni őket, azt a tárolt másolathoz irányította. Ez az evolúciós előny azért nem maradt az Oracle sajátja, az SQL 2005 enterprise babérokra töréséhez elengedhetetlen volt egy ilyen vagy hasonló megoldás megjelenése. Végül az ilyen mellett döntöttek. Mondhatni, hogy koppintották az ötletet, én sokkal inkább a migráció megkönnyítésére szavazok :)<br />
   Szóval az SQL Server 2005-ben megjelent a SNAPSHOT isolation level, amit egyrészt külön be kell kapcsolni az adatbázisban, másrészt kétféleképpen használható: használhatjuk teljesen új izolációs szintként, akár a korábban emlegetett kettőt, illetve lecserélhetjük a read committed izolációs szintet adatbázis szinten snapshot működésre (miután a snapshotot magát engedélyeztük), azaz ha valaki azt gondolja, hogy a default read committed szinten van, akkor téved, mert valójában snapshoton lesz. Ez tuljadonképpen azt jelenti, hogy akkor a default read committed szint helyett igazából default snapshot (sorverziózós) izolációs szintünk lesz, mint az&#8230; Oracle-ben&#8230; (mondtam már, hogy a migráció&#8230;? :) Persze megrögzött mániákus read committed rajongók számára van kiút (vagy visszaút) innen is.<br />
   Ennyi blabla után némi kód, először egy szálon, csak hogy ízlelgessük az utasításokat:</p>
<pre class="brush: sql;">
USE AdventureWorks
-- (1) Ha nem mondunk semmi extrát...
SELECT * FROM Sales.Currency
-- (2) ...akkor az olyan, mintha azt mondtuk volna, hogy
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT * FROM Sales.Currency
-- (Már ha az adatbázisunkon nem állítottuk be a READ_COMMITTED_SNAPSHOT értéket, amit nézzünk is meg, az ALLOW_SNAPSHOT_ISOLATION opcióval együtt.)
SELECT * FROM sys.databases
WHERE name = 'AdventureWorks'
-- Tekintsük meg a snapshot_isolation_state, snapshot_isolation_state_desc és is_read_committed_snapshot_on oszlopok tartalmát.
-- Alaphelyzetben minden ki van kapcsolva.
</pre>
<p>Persze ez az egész akkor lesz érdekes, amikor több tranzakciónk fut egyszerre. Úgyhogy nyissunk három query ablakot (A és B konkurrálgatnak, C-ben pedig megfigyelünk), és teszteljünk (a kommentben lévő sorszámok jelzik a futtatási sorrendet, a betűk pedig a processzeket):</p>
<pre class="brush: sql;">
-- (1) A
USE AdventureWorks
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN
SELECT * FROM Sales.Currency WITH (HOLDLOCK)
WHERE CurrencyCode = 'HUF'
</pre>
<pre class="brush: sql;">
-- (2) B
USE AdventureWorks
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN
SELECT * FROM Sales.Currency WITH (HOLDLOCK)
WHERE CurrencyCode = 'HUF'
</pre>
<p>Ezek eddig boldogan lefutottak. Az explicit read committed deklaráció amúgy felesleges, a <strong><em>with (holdlock)</em></strong> locking hint és a tranzakció pedig azért vannak ott, hogy meg tudjuk nézni az allokált lockokat, különben azok azonnal eltűnnének ahogy lefutott a lekérdezés.</p>
<pre class="brush: sql;">
-- (3) C
select request_session_id, resource_type, resource_database_id, resource_description, request_mode, request_type, request_status from sys.dm_tran_locks
where request_session_id in (61, 62)
order by request_session_id
</pre>
<p>Látszik, hogy mindenen két lock van, és a rekordunk KEY alapján van lockolva, mégpedig shared (S) formában, azaz többen is olvashatják egyszerre (mint mi most). (61 és 62 az A és B ablakok SPID-jei, igény szerint módosítandóak)<br />
Próbáljunk meg írni:</p>
<pre class="brush: sql;">
-- (4) B
ROLLBACK
BEGIN TRAN
UPDATE Sales.Currency
SET [Name] = 'Magyar Forint'
WHERE CurrencyCode = 'HUF'
</pre>
<p>A B szál nem fut le, futva marad, mivel szeretné exkluzív (X) lockolni a módosításhoz a rekordot, de azon &#8211; mivel éppen olvassák &#8211; S lock van, úgyhogy várni kell. Hogy néz ez ki a lockok nyelvén?</p>
<pre class="brush: sql;">
-- (5)
select request_session_id, resource_type, resource_database_id, resource_description, request_mode, request_type, request_status from sys.dm_tran_locks
where request_session_id in (61, 62)
order by request_session_id
</pre>
<p>A B szál tett egy update (U) lockot a S lock mellé a rekordra, és szeretné megkapni az X lockját, de az CONVERT státuszban maradt, mert az S mellé nem fér oda az X (általában: semmi mellé nem fér oda az X). Dobjuk el az A szál által nyitott tranzakciót, hadd fusson ez tovább, és rögtön kérdezzünk is egyet:</p>
<pre class="brush: sql;">
-- (6) A
ROLLBACK
SELECT * FROM Sales.Currency
WHERE CurrencyCode = 'HUF'
</pre>
<p>Megfordultak a szerepek, most A vár B-re, aki a ROLLBACK kiadásakor hirtelen lefutott, és betette az X lockját végre. Nézzük is meg:</p>
<pre class="brush: sql;">
-- (7) C
select request_session_id, resource_type, resource_database_id, resource_description, request_mode, request_type, request_status from sys.dm_tran_locks
where request_session_id in (61, 62)
order by request_session_id
</pre>
<p>Látjuk, hogy az A szálnak WAIT-ben maradt a S lockja. Rollbackeljük B tranzakcióját is! (Grétsy tanár úr sem mondta volna szebben)</p>
<pre class="brush: sql;">
-- (8) B
ROLLBACK
</pre>
<p>(A 9-es lépés eltűnt, mivel kivettem a tranzakció nyitását a 6-osból, így nem kell rollback.)<br />
Most nézzük meg az uncommitted szintet is! Nyissunk egy update tranzakciót:</p>
<pre class="brush: sql;">
-- (10) B
BEGIN TRAN
UPDATE Sales.Currency
SET [Name] = 'Magyar Forint'
WHERE CurrencyCode = 'HUF'
</pre>
<p>A (6)-nál már láttuk, hogy ilyenkor kiakadunk read committed szinten. Nézzük mi történik uncommitted esetben:</p>
<pre class="brush: sql;">
-- (11) A
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM Sales.Currency
WHERE CurrencyCode = 'HUF'
</pre>
<p>Simán lefutott, és visszaadta azt az értéket, amit a B tranzakción belül adtunk meg, ami még nyitott, és nincs kommittálva. Megállapíthatjuk, hogy találó nevet kapott a read uncommitted. Rollbackeljük a tranzakciót&#8230;</p>
<pre class="brush: sql;">
-- (12) B
ROLLBACK
</pre>
<p>&#8230; és kérdezzük le megint ugyanezt:</p>
<pre class="brush: sql;">
-- (13) A
SELECT * FROM Sales.Currency
WHERE CurrencyCode = 'HUF'
</pre>
<p>Most a megfelelő, konzisztens értéket kapjuk vissza.<br />
Lépjünk tovább a SQL 2005 nagyszerű újítása, a nem lockokkal operáló snapshot szint felé:</p>
<pre class="brush: sql;">
-- (14) B
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
SELECT * FROM Sales.Currency
WHERE CurrencyCode = 'HUF'
</pre>
<p>Szép piros outputot kapunk, melyben elmeséli a SSMS, hogy szerinte nem jó vicc olyan adatbázisban snapshot izolációs szintet használni, ahol nincs engedélyezve. Nézzük meg ezt<br />
T-SQL-ből is (15a), majd engedélyezzük (15b) egy egyszerű alter database paranccsal a snapshotot:</p>
<pre class="brush: sql;">
-- (15a) C
SELECT name, database_id, snapshot_isolation_state_desc, is_read_committed_snapshot_on FROM sys.databases
WHERE name = 'AdventureWorks'
-- (15b) C
ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION ON

SELECT name, database_id, snapshot_isolation_state_desc, is_read_committed_snapshot_on FROM sys.databases
WHERE name = 'AdventureWorks'
</pre>
<p>Most még egyszer ugorjunk neki az előzőnek:</p>
<pre class="brush: sql;">
-- (16) B
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
SELECT * FROM Sales.Currency
WHERE CurrencyCode = 'HUF'
</pre>
<p>A transaction isolation level már be lett állítva az előbb, hiszen az a session tulajdonsága, nem az adatbázisé, nem is tudom miért írtam ki még egyszer&#8230; Na, kezdjük újra az író-olvasó találkozót, és írjunk egyet az A szálon:</p>
<pre class="brush: sql;">
-- (17) A
BEGIN TRAN
UPDATE Sales.Currency
SET [Name] = 'Magyar Forint'
WHERE CurrencyCode = 'HUF'
</pre>
<p>Nézzük meg, melyik szinten mit látunk B-ben. (18a)-t és (18b)-t külön futtassuk:</p>
<pre class="brush: sql;">
-- (18a) B
SELECT * FROM Sales.Currency
WHERE CurrencyCode = 'HUF'

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM Sales.Currency
WHERE CurrencyCode = 'HUF'

-- (18b) B
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT * FROM Sales.Currency
WHERE CurrencyCode = 'HUF'

USE tempdb
</pre>
<p>Az első esetben a snapshot szinten az utolsó kommitolt értéket olvashattuk ki, várakozás nélkül. Ez a sorverziózás áldása, nem kell várakozni vagy szemetet olvasni. A második esetben az uncommitted szinten kiolvastunk valamit, ami még nincs is hivatalosan az addatbázisban. A harmadik esetben pedig lógva maradtunk, hiszen az X lock megakasztott minket. Rollbackeljük a tranzakciónkat. (a USE tempdb-ről később)</p>
<pre class="brush: sql;">
-- (19) A
ROLLBACK
USE tempdb
</pre>
<p>És most, hogy mindkét processzünket kiraktuk az AdventureWorksből, megtehetjük azt, ami nekem nagy kedvencem ebben az egész játékban:</p>
<pre class="brush: sql;">
-- (20) C
ALTER DATABASE AdventureWorks SET READ_COMMITTED_SNAPSHOT ON
</pre>
<p>Ehhez a művelethez nem lehet más az adatbázisban, csak az az egy processz, amelyik kiadja a parancsot, ezért mentek át a többiek a tempdb-be. És mostantól kezdve amikor az mondjuk, hogy read committed, akkor az SQL Server 2005 az AdventureWorks adatbázisban sorverziózást fog használni. Játsszuk újra az előző ki mit lát játékot:</p>
<pre class="brush: sql;">
-- (21) A
USE AdventureWorks
BEGIN TRAN
UPDATE Sales.Currency
SET [Name] = 'Magyar Forint'
WHERE CurrencyCode = 'HUF'
</pre>
<p>És kérdezzük végig a három szintet:</p>
<pre class="brush: sql;">
-- (22) B
USE AdventureWorks
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM Sales.Currency
WHERE CurrencyCode = 'HUF'

SET TRANSACTION ISOLATION LEVEL SNAPSHOT
SELECT * FROM Sales.Currency
WHERE CurrencyCode = 'HUF'

SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT * FROM Sales.Currency
WHERE CurrencyCode = 'HUF'
-- hoppa :)
</pre>
<p>Jé, most lefut a read committed is, pedig neki várnia kellene az X lock miatt&#8230; de nem teszi&#8230; mert ő egy álruhás snapshot igazából. Ezt a kis trükköt egyedül a sys.databases-ből lehet kideríteni (lásd (15a)), mert ha a sys.dm_exec_sessions nézetből kérdezzük, ott is read committed a szint.</p>
<pre class="brush: sql;">
-- (23) C
SELECT name, database_id, snapshot_isolation_state_desc, is_read_committed_snapshot_on FROM sys.databases
WHERE name = 'AdventureWorks'
</pre>
<p>Tüntessük el játszadozásunk nyomait:</p>
<pre class="brush: sql;">
-- (24) A
ROLLBACK
</pre>
<pre class="brush: sql;">
kill 61
kill 62
ALTER DATABASE AdventureWorks SET READ_COMMITTED_SNAPSHOT OFF
ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION OFF
</pre>
<p>Ennyit elsőre, később majd az innen kimaradt, konkurrenciát csökkentő szintekről is esik némi szó, de ezt is két hónapja kezdtem el írni :)</p>
<p>A demo script letölthető<a href='http://blog.rollback.hu/wp-content/uploads/2008/07/konkurrenciai.zip'><strong>itt</strong></a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.rollback.hu/2008/07/tranzakcio-izolacios-szintek-avagy-konkurrencia-mssql-modra-i/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

