Co potřebujete, je uzamykání . Transakce skutečně „nejsou nezbytně nutné“.
Můžete si vybrat mezi "pesimistickým zamykáním" a "optimistickým zamykáním". Rozhodnutí o tom, která z těchto dvou možností je na vás a musí být vyhodnoceno v zásadě s ohledem na:
- úroveň souběžnosti, kterou máte
- doba trvání nezbytných atomických operací v databázi
- složitost celé operace
Doporučuji přečíst si tyto dva, abyste si vytvořili představu o tom, co se týká:
- Optimistické vs. pesimistické zamykání
- Optimistické zamykání v MySQL (zde několik příkladů, které ukazují, že transakce nejsou nezbytně nutné)
Příklad pro lepší vysvětlení
Možná to není tak elegantní, ale je to pouze příklad, který ukazuje, jak je možné dělat vše bez transakce (a dokonce bez omezení UNIQUE). Co je třeba udělat, je použít následující kombinovaný statemet INSERT + SELECT a po jeho provedení zkontrolovat počet ovlivněných řádků. Pokud je počet ovlivněných řádků 1, uspěl v opačném případě (pokud je 0), došlo ke kolizi a druhá strana vyhrála.
INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT @startTime, @endTime, @uid, @group, @message, @deviceId
FROM `slot`
WHERE NOT EXISTS (
SELECT `id` FROM `slot`
WHERE `start` <= @endTime AND `end` >= @startTime
AND `devices_id` = @deviceId)
GROUP BY (1);
Toto je příklad optimistického zamykání získaného bez transakcí a s jedinou operací SQL.
Jak je psáno, má problém, že v slot
již musí být alespoň jeden řádek tabulky, aby fungovala (jinak klauzule SELECT vždy vrátí prázdnou sadu záznamů a v takovém případě se nic nevloží evei, pokud nedojde ke kolizi. Existují dvě možnosti, jak to skutečně zprovoznit:
- vložte do tabulky jeden fiktivní řádek, možná s datem v minulosti
-
přepište tak, aby hlavní klauzule FROM odkazovala na jakoukoli tabulku, která má alespoň jeden řádek, nebo lépe vytvořte jednu malou tabulku (může se jmenovat
dummy
) pouze s jedním sloupcem a pouze jedním záznamem v něm a přepište jej následovně (všimněte si, že již není potřeba klauzule GROUP BY)INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`) SELECT @startTime, @endTime, @uid, @group, @message, @deviceId FROM `dummy` WHERE NOT EXISTS ( SELECT `id` FROM `slot` WHERE `start` <= @endTime AND `end` >= @startTime AND `devices_id` = @deviceId);
Zde následuje řada pokynů, které pokud jednoduše zkopírujete/vložíte, ukáže myšlenku v akci. Předpokládám, že v polích int kódujete datum/časy jako číslo se zřetězenými číslicemi data a času.
INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
VALUES (1008141200, 1008141210, 11, 2, 'Dummy Record', 14)
INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141206, 1408141210, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
SELECT `id` FROM `slot`
WHERE `start` <= 1408141210 AND `end` >= 1408141206
AND `devices_id` = 14)
GROUP BY (1);
INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141208, 1408141214, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
SELECT `id` FROM `slot`
WHERE `start` <= 1408141214 AND `end` >= 1408141208
AND `devices_id` = 14)
GROUP BY (1);
INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141216, 1408141220, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
SELECT `id` FROM `slot`
WHERE `start` <= 1408141220 AND `end` >= 1408141216
AND `devices_id` = 14)
GROUP BY (1);
SELECT * FROM `slot`;
Toto je zjevně extrémní příklad optimistického zamykání, ale ve finále je velmi efektivní, protože vše probíhá pouze s jednou instrukcí SQL a s nízkou interakcí (výměnou dat) mezi databázovým serverem a php kódem. Dále neexistuje prakticky žádné "skutečné" zamykání.
...nebo s pesimistickým zamykáním
Stejný kód se může stát dobrou implementací pesimistického zamykání, která obklopuje explicitní instrukce pro zamykání/odemykání tabulky:
LOCK TABLE slot WRITE, dummy READ;
INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT @startTime, @endTime, @uid, @group, @message, @deviceId
FROM `dummy`
WHERE NOT EXISTS (
SELECT `id` FROM `slot`
WHERE `start` <= @endTime AND `end` >= @startTime
AND `devices_id` = @deviceId);
UNLOCK TABLES;
Samozřejmě v tomto případě (pesimistické zamykání) lze SELECT a INSERT oddělit a mezitím spustit nějaký php kód. Tento kód však zůstává velmi rychle proveditelný (žádná výměna dat s php, žádný přechodný php kód), a tak je trvání Pesimistického zámku nejkratší možné. Udržet pesimistický zámek co nejkratší je klíčovým bodem, aby se zabránilo zpomalení aplikace.
Každopádně musíte zkontrolovat návratovou hodnotu počtu ovlivněných záznamů, abyste věděli, zda uspěla, protože kód je prakticky stejný, takže informace o úspěchu/neúspěchu získáte stejným způsobem.
Zde http://dev.mysql.com/doc/ refman/5.0/en/insert-select.html říkají, že "MySQL nepovoluje souběžné vkládání pro příkazy INSERT ... SELECT" takže pesimistický zámek by neměl být potřeba, ale každopádně to může být dobrá volba, pokud si myslíte, že se to v budoucích verzích MySQL změní.
Jsem "optimistický" že se to nezmění;-)