Podmínka kde bude respektována během závodní situace, ale musíte být opatrní, jak kontrolujete, abyste viděli, kdo vyhrál závod.
Zvažte následující ukázku toho, jak to funguje a proč musíte být opatrní.
Nejprve nastavte několik minimálních tabulek.
CREATE TABLE table1 (
`id` TINYINT UNSIGNED NOT NULL PRIMARY KEY,
`locked` TINYINT UNSIGNED NOT NULL,
`updated_by_connection_id` TINYINT UNSIGNED DEFAULT NULL
) ENGINE = InnoDB;
CREATE TABLE table2 (
`id` TINYINT UNSIGNED NOT NULL PRIMARY KEY
) ENGINE = InnoDB;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);
id
hraje roli id
ve vaší tabulce updated_by_connection_id
funguje jako assignedPhone
a locked
jako reservationCompleted
.
Nyní začneme závodní test. Měli byste mít otevřená 2 okna příkazového řádku/terminálu, připojená k mysql a pomocí databáze, kde jste tyto tabulky vytvořili.
Připojení 1
start transaction;
Připojení 2
start transaction;
Připojení 1
UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
Připojení 2
UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
Připojení 2 nyní čeká
Připojení 1
SELECT * FROM table1 WHERE id = 1;
commit;
V tomto okamžiku je připojení 2 uvolněno, aby pokračovalo, a výstupem je následující:
Připojení 2
SELECT * FROM table1 WHERE id = 1;
commit;
Všechno vypadá dobře. Vidíme, že ano, klauzule WHERE byla respektována v situaci závodu.
Důvod, proč jsem řekl, že musíte být opatrní, je ten, že ve skutečné aplikaci nejsou věci vždy tak jednoduché. V rámci transakce MŮŽETE provádět další akce, které mohou ve skutečnosti změnit výsledky.
Obnovme databázi pomocí následujícího:
delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);
A nyní zvažte tuto situaci, kdy se SELECT provede před UPDATE.
Připojení 1
start transaction;
SELECT * FROM table2;
Připojení 2
start transaction;
SELECT * FROM table2;
Připojení 1
UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
Připojení 2
UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
Připojení 2 nyní čeká
Připojení 1
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
V tomto okamžiku je připojení 2 uvolněno, aby pokračovalo, a výstupem je následující:
Dobře, uvidíme, kdo vyhrál:
Připojení 2
SELECT * FROM table1 WHERE id = 1;
Počkej co? Proč je locked
0 a updated_by_connection_id
NULL??
Toto je opatrnost, kterou jsem zmínil. Viník je vlastně způsoben tím, že jsme na začátku udělali selekci. Abychom získali správný výsledek, mohli bychom spustit následující:
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
Pomocí SELECT ... FOR UPDATE můžeme získat správný výsledek. To může být velmi matoucí (jak to bylo původně pro mě), protože SELECT a SELECT ... FOR UPDATE dávají dva různé výsledky.
Důvodem, proč k tomu dochází, je výchozí úroveň izolace READ-REPEATABLE
. Když je proveden první SELECT, hned po start transaction;
, vytvoří se snímek. Všechna budoucí neaktualizovaná čtení budou provedena z tohoto snímku.
Pokud tedy po aktualizaci naivně VYBERTE, stáhne informace z původního snímku, který je před řádek byl aktualizován. Provedením SELECT ... FOR UPDATE jej přinutíte získat správné informace.
V reálné aplikaci to však může být problém. Řekněme například, že váš požadavek je zabalen do transakce a po provedení aktualizace chcete odeslat nějaké informace. Shromažďování a výstup těchto informací může být zpracován pomocí samostatného, opakovaně použitelného kódu, který NECHCETE zasypávat klauzulemi FOR UPDATE „pro každý případ“. To by vedlo ke spoustě frustrace kvůli zbytečnému zamykání.
Místo toho budete chtít jít jinou cestou. Zde máte mnoho možností.
Jedním z nich je ujistit se, že transakci potvrdíte po dokončení UPDATE. Ve většině případů je to pravděpodobně nejlepší a nejjednodušší volba.
Další možností je nepokoušet se pomocí SELECT určit výsledek. Místo toho můžete být schopni přečíst dotčené řádky a použít to (1 řádek aktualizován oproti aktualizaci 0 řádků) k určení, zda byla UPDATE úspěšná.
Další možností, kterou často používám, protože rád ponechávám jeden požadavek (jako požadavek HTTP) plně zabalený do jediné transakce, je ujistit se, že první příkaz provedený v transakci je buď UPDATE nebo VYBERTE ... PRO AKTUALIZACI . To způsobí, že snímek NEBUDE pořízen, dokud nebude povoleno pokračovat připojení.
Pojďme znovu resetovat naši testovací databázi a uvidíme, jak to funguje.
delete from table1;
INSERT INTO table1
(`id`,`locked`)
VALUES
(1,0);
Připojení 1
start transaction;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
Připojení 2
start transaction;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
Připojení 2 nyní čeká.
Připojení 1
UPDATE table1
SET locked = 1,
updated_by_connection_id = 1
WHERE id = 1
AND locked = 0;
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
Připojení 2 je nyní uvolněno.
Připojení 2
+----+--------+--------------------------+
| id | locked | updated_by_connection_id |
+----+--------+--------------------------+
| 1 | 1 | 1 |
+----+--------+--------------------------+
Zde můžete ve skutečnosti nechat svůj kód na straně serveru zkontrolovat výsledky tohoto SELECT a vědět, že je přesný, a ani nepokračovat v dalších krocích. Ale pro úplnost skončím jako předtím.
UPDATE table1
SET locked = 1,
updated_by_connection_id = 2
WHERE id = 1
AND locked = 0;
SELECT * FROM table1 WHERE id = 1;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
commit;
Nyní můžete vidět, že v Connection 2 SELECT a SELECT ... FOR UPDATE dávají stejný výsledek. Důvodem je, že snímek, ze kterého SELECT čte, byl vytvořen až po potvrzení připojení 1.
Takže zpět k vaší původní otázce:Ano, klauzule WHERE je ve všech případech kontrolována příkazem UPDATE. Musíte však být opatrní se všemi SELECTy, které můžete provádět, abyste se vyhnuli nesprávnému určení výsledku této AKTUALIZACE.
(Ano, další možností je změnit úroveň izolace transakcí. Nicméně s tím nemám zkušenosti a žádné problémy, které by mohly existovat, takže se do toho nehodlám pouštět.)