[[oktatas:adatbázis-kezelés|< Adatbázis-kezelés]] ====== Tranzakció ====== * **Szerző:** Sallai András * Copyright (c) Sallai András, 2014, 2021 * Licenc: GNU Free Documentation License 1.3 * Web: https://szit.hu ===== Fogalom ===== ==== Inkonzisztens ==== Egymásnak ellentmondó adatok ===== Osztott erőforrások ===== Az erőforrásainkat szeretjük megosztani több felhasználó között, a gazdaságosabb, hatékonyabb működés érdekében. Egy erőforrás használata konfliktushoz vezethet, ha egyszerre akarja használni két vagy több felhasználó. Az adatbázisainkat általában egyszerre többen használjuk, az egyes adatbázis műveletek szintén konfliktusba kerülhetnek egymással. {{:oktatas:adatbázis-kezelés:osztot_eroforrasok.png|}} ===== ACID elvek ===== * Atomicity - **atomicitás** - [ˌæ.təm.ˈɪ.sɪ.ti] * A műveletek egybe tartoznak. Vagy **mindent** végrehajtok **vagy semmit**. * Consistency - **konzisztencia** - kən.ˈsɪ.stən.si] * Konzisztens állapot - műveletek előtt naplózás. * Az adatok nincsenek ellentmondásban egymással. * Isolation - **izoláció** - [ˌaɪ.sə.ˈleɪʃ.n̩] * Egy művelet nem függ másoktól * Durability - **tartósság** - [ˌdjʊə.rə.ˈbɪ.lɪ.ti] * Az elvégzett műveletek eredménye legyen tartós. ===== A tranzakcióról ===== Egyben kezelt műveletsor, amelyre teljesülnek az ACID elvek. Az adatok védelme érdekében a tranzakciókezelés során két feladat van: * a tranzakciók, korrekt párhuzamos végrehajtása * tevékenységek naplózása ===== Tranzakció példa ===== A tranzakciók tárgyalásánál gyakran használnak egy banki átutalást példaként: - Megnézzük létezik-e a célszámla - Megnézzük a forrásszámlán van-e elég pénz - Csökkentjük a forrásszámla egyenlegét - Növeljük a célszámla egyenlegét ===== Dolgozók ===== {{:oktatas:adatbazis-kezeles:dolgozok.png?200|}} ^ folyamat1 ^^ | beolvas Béla fizetése | 5000 | | Béla fizetését megemeljük 2000-rel | 7000 | | Kiírjuk a fizetést | 7000 | ^ folyamat2 ^^ | beolvas Béla fizetése | 7000 | | Béla fizetését megemeljük 2000-rel | 9000 | | Kiírjuk a fizetést | 9000 | | f1 | beolvas Béla fizetése | 5000 | | f1 | Béla fizetését megemeljük 2000-rel | 7000 | | f2 | beolvas Béla fizetése | 5000 | | f2 | Béla fizetését megemeljük 2000-rel | 7000 | | f1 | kiír Béla fizetése | 7000 | | f2 | kiír Béla fizetése | 7000 | ==== Megoldás ==== * sorosítás * zárolás ==== Tranzakció ==== Az utasítások oszthatatlanul hajtódnak végre. Jellemzők: * zárolást alkalmazunk * a külső tranzakció vár * véglegesítés ===== Tranzakció példa ===== create database tran character set utf8 collate utf8_hungarian_ci; create table dolgozok ( az int not null primary key auto_increment, nev varchar(20), fizetes double ); insert into dolgozok (nev, fizetes) values ('Béla', 5000), ('Tibi', 5000); ==== Visszavont ==== start transaction; update dolgozok set fizetes = 16000; update dolgozok set fizetes = fizetes + 10000 where id = 2; select * from dolgozok; rollback select * from dolgozok; ==== Konkurálás ==== Két terminálban: ^ terminál1 ^ terminál2 ^ | select * from dolgozok | select * from dolgozok | ^ terminál1 ^ terminál2 ^ | start transaction | start transaction | ^ terminál1 ^ terminál2 ^ | update dolgozok \\ set fizetes=11000 \\ az = 2 | update dolgozok \\ set fizetes=12000 \\ az = 1 | | select * from dolgozok | select * from dolgozok | Mivel a két tranakció el van szigetelve egymástól, a terminál1-ben nem látszik még a terminál2 módosítása, és a terminál2-ben nem látszik még termnál1 módoítása. ^ terminál1 ^ terminál2 ^ | update dolgozok \\ set nev='Emese' \\ az=1 | | A terminál1 várakozik, mert az 1-s rekordot a terminál2 már módosította. ^ terminál1 ^ terminál2 ^ | | commit | A terminál2 végzett. Nézzük meg baloldalon: ^ terminál1 ^ terminál2 ^ | select * from dolgozok | | Nézzük meg a terminál2-ben: ^ terminál1 ^ terminál2 ^ | | select * from dolgozok | A terminál2-ben még nem látható a terminál1 Emese névváltoztatása. ^ terminál1 ^ terminál2 ^ | commit | | | select * from dolgozok | select * from dolgozok | Most már az állapotok egyeznek. ==== Deadlock ==== ^ terminál1 ^ terminál2 ^ | start transaction | start transaction | ^ terminál1 ^ terminál2 ^ | update dolgozok \\ set nev="Irén" \\ where az=1 | update dolgozok \\ set nev="Kati" \\ where az=2 | ^ terminál1 ^ terminál2 ^ | update dolgozok \\ set fizetes=20000 \\ where az=2 | | A terminál1 várakozásra kényszerül. Terminál2-ben: ^ terminál1 ^ terminál2 ^ | | update dolgozok \\ set fizetes=40000 \\ where az=1 | Most keresztbe várakoznak. ===== MySQL tranzakció ===== Példa: Legyen a következő két tábla: CREATE TABLE IF NOT EXISTS `Dolgozok` ( `az` int(11) NOT NULL, `nev` varchar(28) DEFAULT NULL, `anyja_neve` varchar(23) DEFAULT NULL, `telepules` varchar(20) DEFAULT NULL, `lakcim` varchar(46) DEFAULT NULL, `fizetes` double DEFAULT NULL, `szuletes` date DEFAULT NULL, `jutalom` double DEFAULT NULL ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `Naplo` ( `az` int(11) NOT NULL, `telepules` varchar(100) COLLATE utf8_hungarian_ci NOT NULL, `osszeg` double NOT NULL, `megjegyzes` text COLLATE utf8_hungarian_ci NOT NULL, `datum` date NOT NULL ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_hungarian_ci; Feltöltjük, a Dolgozok táblát, úgy hogy legyen miskolci dolgozó, majd a következő SQL utasítást adjuk ki: start transaction; SELECT @A:=sum(fizetes) FROM `Dolgozok` where telepules="Miskolc"; insert into Naplo (osszeg, telepules, datum) values (@A, "Miskolc", "2014-09-23"); commit; ===== Izolációs szintek ===== ==== Izolációs szintek a MySQL tranzakciókban ==== === READ UNCOMMITED === Olyan adatokat is **olvashatunk**, amelyek **még nincsenek rögzítve**. Piszkos olvasás. A tranzakció vége előtt már olvashatók a nem rögzített adatok. === READ COMMITED === **Olvasáskor** mindig a **már rögzített (commitelt) eredményt kapjuk**. Ha fut egy tranzakció ami már változtatott az általunk kívánatos sorokon, akkor mi a régi eredményeket kapjuk. Ha sokan írják az adatbázist, itt lassulás következhet be, más folyamatok arra **várnak**, hogy **az egész tábla frissítése befejeződjön.** Előforulhat a **Phantom read** és **Non-repeatable reads** hiba. A **Phantom read** jelentése a következő: Előfordulhat, hogy két lekérdezés lefut és más eredményt adnak. A **Non-repeatable reads** azt jelenti, hogy egy tranzakció még nem fejeződött be, amikor egy másik már megváltoztatta az olvasott adatokat. Alapértelmezett szint MS SQL Server esetén. === REPEATABLE READ === Alapértelmezett szintje az InnoDB tábláknak. **Csak rögzített rekordokat olvasunk.** Vagyis **nem várunk a teljes tábla frissítésére**, ami már rögzített rekord az olvasható is. Előfordulhat a **Phantom read** hiba. === SERIALIZABLE === Más tranzakció nem frissíthet, nem nézhet a táblába, amíg véget nem ér egy tranzakció. Olyan **mint a zárolt (LOCK) tábla**. ==== InnoDB és más rendszerek ==== Az InnoDB mind a négy szintet támogatja. De vigyázzunk konvertáláskor, mert más adatbázis-kezelők esetleg nem támogatják mind a négy szintet. Pl.: * PostgreSQL * alapértelmezett: REPEATABLE READ * támogatja: REPEATABLE READ and SERIALIZABLE * Oracle * alapértelmezett: READ COMMITTED * támogatott: READ COMMITTED, SERIALIZABLE és a nem szabványos READ ONLY) * MS SQL * READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SNAPSHOT , SERIALIZABLE ==== Izolációs szint beállítása ==== A my.cnf állományban a [mysqld] szakaszban állíthatjuk: [mysqld] transaction-isolation = {READ-UNCOMMITTED | READ-COMMITTED | REPEATABLE-READ | SERIALIZABLE} Vagy egy SQL paranccsal is megadhatjuk amit szeretnénk: SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE} A tranzakciós szint nem állítható tranzakción belül. Ha nem használjuk a session vagy a global kulcsot, a beállítás egy select elvégzéséig él csak. Lehetséges beállítások: set transaction isolation level read uncommitted; set transaction isolation level read committed; set transaction isolation level repeatable read; set transaction isolation level serializable; Ellenőrzés: select @@global.tx_isolation, @@tx_isolation Különbségek a session, global és mindkettő elhagyása között: * mindkettő elhagyása: * Csak egyetlen select utasításig érvényes. * set transaction isolation level read uncommitted; * session * Csak adott belépő számára érvényes. * set session transaction isolation level read uncommitted; * global * Minden új belépőnek érvényes. * set global transaction isolation level read uncommitted; ===== Izolációs szint tesztelése ===== ==== Teszttábla létrehozása ==== create database tran character set utf8 collate utf8_hungarian_ci; create table dolgozok ( az int not null primary key auto_increment, nev varchar(20), fizetes double ); insert into dolgozok (nev, fizetes) values ('Béla', 5000), ('Tibi', 5000); ==== Read Uncommitted ==== Indítsunk egy terminálban egy kapcsolatot: start transaction; update dolgozok set fizetes=2000; Egy másik terminálban, vizsgáljuk meg milyen izolációs szint van beállítva a munkameneten: select @@tx_isolation; Kérdezzük le a tran adatbázis dolgozók táblájának tartalmát: select * from dolgozok; Állítsuk be a **read uncommitted** izolációs szintet a munkamenetre: set session transaction isolation level read uncommitted; Most kérdezzük le újból: select * from dolgozok; Az első lekérdezésnél, ha az izolációs szint **repeatable read** volt, az update eredménye még nem látszik. A második lekérdezésnél, azonban meg kell jelenjen az első terminálon beállított új érték. A **read uncommitted** izolációs szint működik.