Jak správně implementovat optimistické zamykání v MySQL?
Náš tým usoudil, že musíme udělat # 4 níže, jinak existuje riziko, že jiné vlákno může aktualizovat stejnou verzi záznamu, ale rádi bychom ověřili, že je to nejlepší způsob, jak to udělat.
SELECT FOR UPDATE
v záznamu, který se chystáme aktualizovat, abychom serializovali, kdo může provádět změny v záznamu, který se pokoušíme aktualizovat.Abychom to vyjasnili, snažíme se zabránit tomu, aby dvě vlákna, která vyberou stejný záznam ve stejném časovém okně, kde popadnou stejnou verzi záznamu, přepsala všechny ostatní, pokud by se pokusily záznam aktualizovat současně. Věříme, že pokud neuděláme # 4, existuje šance, že pokud obě vlákna vstoupí do svých příslušných transakcí současně (ale zatím nevydaly své aktualizace), když jdou na aktualizaci, druhé vlákno, které použije UPDATE ... kde verze = X bude fungovat na starých datech.
Máme pravdu v myšlení, že při aktualizaci musíme provést toto pesimistické zamykání, přestože používáme pole verzí/optimistické zamykání?
Váš vývojář se mýlí. Potřebujete buď SELECT ... FOR UPDATE
nebo verzování řádků, ne obojí.
Zkuste to a uvidíte. Otevřete tři relace MySQL (A)
, (B)
a (C)
do stejné databáze.
V (C)
problém:
CREATE TABLE test(
id integer PRIMARY KEY,
data varchar(255) not null,
version integer not null
);
INSERT INTO test(id,data,version) VALUES (1,'fred',0);
BEGIN;
LOCK TABLES test WRITE;
V obou (A)
a (B)
vydejte UPDATE
, který testuje a nastavuje verzi řádku, v každém mění text winner
, abyste viděli, která relace je která:
-- In (A):
BEGIN;
UPDATE test SET data = 'winnerA',
version = version + 1
WHERE id = 1 AND version = 0;
-- in (B):
BEGIN;
UPDATE test SET data = 'winnerB',
version = version + 1
WHERE id = 1 AND version = 0;
Nyní v (C)
, UNLOCK TABLES;
k uvolnění zámku.
(A)
a (B)
bude závodit o uzamčení řady. Jeden z nich vyhraje a dostane zámek. Druhý blokuje zámek. Vítěz, který dostal zámek, bude pokračovat ve změně řádku. Za předpokladu (A)
je vítěz, nyní můžete vidět změněný řádek (stále nezávazný, takže není viditelný pro jiné transakce) s SELECT * FROM test WHERE id = 1
.
Nyní COMMIT
ve výherní relaci, řekněte (A)
.
(B)
získá zámek a bude pokračovat v aktualizaci. Verze se však již neshoduje, takže se nezmění žádné řádky, jak je uvedeno ve výsledku počtu řádků. Účinek měl pouze jeden UPDATE
a klientská aplikace může jasně vidět, které UPDATE
uspěly a které selhaly. Není nutné žádné další zamykání.
Viz protokoly relací v Pastebinu zde . Použil jsem mysql --Prompt="A> "
atd., aby bylo snadné rozeznat rozdíl mezi relacemi. Zkopíroval jsem a vložil výstup vložený v časové posloupnosti, takže to není úplně surový výstup a je možné, že jsem mohl udělat chyby při jeho kopírování a vkládání. Vyzkoušejte to sami.
Pokud byste nepřidali pole s řádkem, , pak byste potřebovali na SELECT ... FOR UPDATE
aby bylo možné spolehlivě zajistit objednávání.
Pokud o tom přemýšlíte, SELECT ... FOR UPDATE
je zcela nadbytečné , pokud okamžitě děláte UPDATE
bez opakovaného použití dat z SELECT
, nebo pokud používáte verzi verzí řádků. Klávesa UPDATE
přesto uzamkne. Pokud někdo jiný aktualizuje řádek mezi vaším čtením a následným zápisem, vaše verze se již nebude shodovat, takže aktualizace selže. Takto funguje optimistické zamykání.
Účel SELECT ... FOR UPDATE
je:
SERIALIZABLE
nebo verzi řádků .Nemusíte používat jak optimistické zamykání (verzování řádků), tak SELECT ... FOR UPDATE
. Použijte jeden nebo druhý.
UPDATE tbl SET owner = $me,
id = LAST_INSERT_ID(id)
WHERE owner = ''
LIMIT 1;
$id = SELECT LAST_INSERT_ID();
Do some stuff (arbitrarily long time)...;
UPDATE tbl SET owner = '' WHERE id = $id;
Není třeba žádných zámků (nikoli tabulkových, ne transakčních), nebo dokonce požadovaných: