Portál AbcLinuxu, 15. května 2025 22:46

Dotaz: Mysql - zamykání "cizích" řádků během transakce

30.7.2011 00:33 brevnos
Mysql - zamykání "cizích" řádků během transakce
Přečteno: 733×
Odpovědět | Admin
Ahoj. V transakci bych potřeboval provádět následující:
1. SELECT id FROM identifikatory WHERE nazev = 'nazev';
// pokud predchozi select vrati id, pak
2. INSERT INTO tabulka VALUES (id, .....)
Tyto operace provádím v transakci s úrovní REPEATABLE READ, ale nemyslím si, že by samotná transakce ochránila bod 2 před tím, aby někdo mezitím smazal dané id. V tabulce tabulka je totiž id jako cizí klíč na tabulku identifikatory, takže ve druhém kroku potřebuju, aby id existovalo. Jakým způsobem mohu zajistit, aby id určitě existovalo ve 2. kroku?
Nástroje: Začni sledovat (0) ?Zašle upozornění na váš email při vložení nového komentáře.

Odpovědi

30.7.2011 01:09 Kit
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
Odpovědět | | Sbalit | Link | Blokovat | Admin
Stačí jen jeden krok:
INSERT INTO tabulka SELECT id, ..... FROM identifikatory WHERE nazev='nazev' LIMIT 1;
Pokud nazev nebude existovat, nový záznam se nevloží.
30.7.2011 01:15 brevnos
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
A nejde to rozdělit a provést to přitom jako atomickou operaci? Jde o to, že takových selectů se stejným id tam mám třeba několik tisíc a ptát se pokaždé jaké to id je bude asi časové náročné.
30.7.2011 01:30 Kit
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
Nebude. Za prvé existuje databázová cache a za druhé máš určitě tabulku identifikatory indexovánu podle sloupce nazev.

Tu časovou náročnost jsem nedávno měřil. Není to tak zlé, jak se povídá. Navíc všech 1000 záznamů můžeš vložit jedním insertem, takže to bude i rychlé.
30.7.2011 22:07 l0gik | skóre: 22
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
To nic nemění na tom, že pokud těch tisíc záznamů poběží každej zvlášť, tak to už rychlé nebude - a vkládat 1000 záíznamů najednou zas nemusí jít kvůli packet size.

V úrovni repeatable reads se zámky se dávají zámky automaticky, takže netřeba řešit. Od toho to je repeatable reads, co se jednou přečte, tak se nemění (a je tedy znovupřečtitelné).
31.7.2011 04:34 brevnos
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
Je to potřeba řešit, repeatable reads "ochraňuje" upravované záznamy z transakce k světu tak, že svět před commitem nevidí změnu. Pomohl příspěvek níže s LOCK IN SHARE MODE.
1.8.2011 12:31 l0gik | skóre: 22
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
Nene, to co píšeš je READ COMMITED, to je izolace o úroveň níže. Ale já to taky nepopsal přesně: Repeatable reads nedává zámky na čtené věci, ale zajišťuje, že do konce transakce v transakci to, co přečteš stejným dotazem.

V této situaci funguje konkrétně tak, že ten, kdo se první pokusí zapsat, tak uspěje, a druhý (ať při snaze změnit FK, nebo při snaze zapsat novou hodnotu se "starým FK" bude čekat na výsledek první transakce, pokud se commitne, tak příkaz neuspěje. Což je pravda, že to není úplně to, co tazatel chtěl, pokud ale implementuje opakování transakce (což je nutné tak jako tak, protože při repeatable reads transakce díky race condition může selhat i z jiných v podstatě neodstranitelných důvodů), tak už tím získá i řešení tohoto problému.

Ale otázka jestli je lepší pesimistic nebo optimistic locking, nemá jednoznačnou odpověď, takže pokud se ten číselník moc nemění, tak klidně zamykat.
1.8.2011 22:19 brevnos
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
protože při repeatable reads transakce díky race condition může selhat i z jiných v podstatě neodstranitelných důvodů)
Jakých třeba? Opakování transakcí nikde implementováno nemám. Vyzkoušel jsem, že transakce v REPEATABLE READS může selhat takto:
sezení 2:
START TRANSACTION
SELECT id FROM hlavni_tabulka WHERE id = 2;


sezení 1:
DELETE FROM hlavni_tabulka WHERE id = 2;


sezení 2:
INSERT INTO vedlejsi_tabulka (id, ...) VALUES (2, ....); // selže kvůli cizímu klíči id tabulky hlavni_tabulka tzn. ačkoliv mi v tomto bodě
dotaz "SELECT id FROM hlavni_tabulka WHERE id = 2" vrací hodnotu, fyzicky už to tam neexistuje a foreign key zajímá
jen poslední verze. Tohle nevím jak vyřešit kromě izolace SERIALIZABLE, která by měla zamknout čtené řádky, ale to bohužel (jak jsem dnes zjistil) nemohu udělat, protože potřebuju,
aby ostatní sezení měla k dispozici RW přístup k řádkům hlavní tabulky během transakce (která může trvat i několik desítek minut - miliony řádků).
O jiných důvodech proč by mělo REPEATABLE READS selhat nevím. Souběh už by měla mít databáze vyřešený ne?
1.8.2011 23:02 kuka
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
Obecne vzdy "vyssi" uroven zabezpeceni transakce neco stoji, extremem je serializable, kde je treba s opakovanim pocitat prakticky vzdy. To ze bude jedne transakci zaruceno repeatable read znamena omezeni ostatnich transakci a naopak. Jedna z transakci tak ma ve vysledku proste smulu a protoze to muze byt "normalni prubeh" a transakce po opetovnem spusteni probehne dobre (jeji kolidujici transakce uz skocila), mela by obvykle aplikace tuto moznost resit. Proto mam osobne radeji explicitni zamykani tam, kde je to nutne.

Mozna ze v tvem konkretnim pripade nemuze kolize nastat, ale je treba to dobre promyslet a na to je treba znat vsechny mozne operace nad konkretnimi tabulkami. Stroj nemuze vedet, k cemu presne ten repeatable read pouzivas - napr. muzes zjistit radek v hlavni tabulce a pokud neexistuje tak ho zalozit. Pak na zaklade toho neco pocitat a pak neco s radkem udelat. Jestlize na zacatku existoval a mezitim ho nekdo smaze, tak ti to selze, ale po retartu transakce uz to projde dobre (pokud tam zase nekdo neskodi), protoze ten radek si tam sam zalozis. Tzn. de facto misto explicitniho zamykani mas implicitni a to co bys pro explicitni zamek resil pri neuspechu zamceni (napr. cekani az se zamceni podari nebo se zjisti ze radek uz neexistuje), resis restartem transakce jako celku. Kazdy pristup se muze hodit v jine situaci.

Mozna trochu zjednodusene pokud je opravdu repeatable reads vecne potreba, tak vetsinou existuje i moznost restartu transakce. Pokud potreba neni, tak je lepsi ji nepouzit a potrebe restartu se vyhnout.

2.8.2011 02:06 l0gik | skóre: 22
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
No, todle je zrovna jeden z případů. Obecně repeatable reads selže, pokud něco změníš transakci pod rukama a tím zrušíš integritu dat. Dál při různejch race condition, např. obě transakce se pokusej změnit stejnej řádek, apod.

I když teď jsem to zkoušel a musím říci, že se to chová divně, IMHO tam má mysql bug, protože todle projde a nemělo by
CREATE TABLE a (a integer) engine innodb;
INSERT INTO a values (1);
když spustím následující řádky synchronně ve dvou transakcích (vždy stejný řádek v jedné a pak v druhé), tak mi ta druhá nezařve, i když by měla (a např. v postgresql zařve) a normálně počká na commit první transakce a pak zahlásí nula modifikovaných řádek.
set session transaction isolation level repeatable read ;
BEGIN;     
SELECT * FROM a;
UPDATE a set a=2 /*v druhé =3*/ where a=1;     
COMMIT
Přitom další SELECT * FROM a; furt tvrdí, že tam v té tabulce je jednička. Tomu tedy rozhodně neříkám repeatable reads, tomu říkám paskvil.

---

Nicméně obecně: pokud chceš mít dlouhé transakce, během kterých ostatní transakce mají RW přístup, nemůžeš zajistit konzistenci. To prostě z principu nelze. Buď máš konzistentní čtení/zápis, pak ale když se sejdou dvě transakce nad stejnými daty, ne vždy jdou uspořádat a tedy je potřeba někdy jednu zrušit a pak zavolat znova. To u dlouhé transakce je blbina, pak ji musíš rozsekat na krátké transakce. Anebo se vykašleš na konzistenci. Zajistit obojí prostě nejde - když té dlouhé transakci změní něco krátká transakce pod rukama, tak to prostě "automaticky" vyřešit nejde.

2.8.2011 03:12 brevnos
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
Mám tam případy, kdy se může najednou modifikovat (respektive většinou DELETE + INSERT) stejný řádek v tabulce, takže k selhání může dojít. Díky za objasnění, nějak jsem nepočítal s tím, že by transakce mohla skončit i v takovém případě jako v tom příkladu. Podle mě by se to správně mělo chovat tak, že se náhodně zvolí, která z transakcí má přednost a ta druhá počká do jejího vykonání - je to celkem logické. K těm dlouhým transakcím - většinou se jedná o smazání celé tabulky (pomocí DELETE, aby se to dalo v mysql zpracovat transakčně), poté INSERT nových řádků, s tím, že bych chtěl, aby se tabulka navenek chovala jako kdyby se nic nedělo (RW dotazy) a po provedení transakce došlo k jejímu přepsání - v těchto případech se mi jedná o konzistenci dat nově vkládaných, jestli bude někdo během transakce měnit tabulku mi nevadí, ale mělo by to jít.
3.8.2011 02:05 brevnos
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
Ještě k těm opakováním transakcí? Jak se to ošetřuje v programu? Viděl jsem nějaké příklady a tam to nechávali v nekonečném cyklu, což je podle mě kravina. Jak jsem dnes zkoumal, tak db může vyhodit během transakce skoro všechny možný chyby a ty se nedají odlišit od chyb stálých tzn. nepoznám jestli transakce skončila kvůli race condition, dead locku nebo třeba kvůli nějaké trvalé chybě, která nemá s předchozími nic společné. Jak to tedy řešit resp. jak se to řeší? Opakovat n-krát?
3.8.2011 16:35 l0gik | skóre: 22
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
No tak u lepší databáze (postgresql např.) jsou selhání transakční izolace hlášený speciální výjimkou, takže to detekovatelný je. U MySQL tomu bohužel tak není - teda deadlock poznáš, ale některý jiný chyby tuším ne. Takže nic lepšího, než n-násobný opakování Ti asi nezbývá.
30.7.2011 02:09 Kit
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
Odpovědět | | Sbalit | Link | Blokovat | Admin
A ještě něco: Pokud by tabulka identifikatory byla stabilní a potřeboval bys větší výkon databáze, zvaž její odstranění. Místo cizího klíče pak použiješ datový typ ENUM. Databáze s ENUM pracuje o něco rychleji a hlavně je s ním mnohem pohodlnější práce. Vně se chová jako varchar, ale uvnitř je to jen jednobajtový (příp. dvoubajtový) identifikátor.
30.7.2011 14:35 kuka
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
Odpovědět | | Sbalit | Link | Blokovat | Admin
SELECT id FROM identifikatory WHERE nazev = 'nazev' LOCK IN SHARE MODE
31.7.2011 04:34 brevnos
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
Díky, tohle je to co hledám.
30.7.2011 22:19 Sten
Rozbalit Rozbalit vše Re: Mysql - zamykání "cizích" řádků během transakce
Odpovědět | | Sbalit | Link | Blokovat | Admin
SELECT id FROM identifikatory WHERE nazev = 'nazev' FOR UPDATE;

Založit nové vláknoNahoru

Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.