Potrzebujesz blokowania . Transakcje rzeczywiście „nie są ściśle potrzebne”.
Możesz wybierać między „pesymistycznym” a „optymistycznym”. Decyzja o tym, która z tych dwóch możliwości należy do Ciebie i należy ją ocenić, biorąc pod uwagę:
- poziom Twojej współbieżności
- czas trwania operacji „niezbędnych atomowych” na bazie danych
- złożoność całej operacji
Polecam przeczytanie tych dwóch, aby zbudować pojęcie o zaangażowanych rzeczach:
- Blokowanie optymistyczne kontra pesymistyczne
- Optymistyczne blokowanie w MySQL (tutaj kilka przykładów, które pokazują, w jaki sposób transakcja nie jest ściśle potrzebna)
Przykład do lepszego wyjaśnienia
To może nie jest takie eleganckie, ale jest tylko przykładem, który pokazuje, jak można zrobić wszystko bez transakcji (a nawet bez ograniczeń UNIQUE). Wystarczy użyć poniższej połączonej instrukcji INSERT + SELECT i po jej wykonaniu aby sprawdzić liczbę wierszy, których dotyczy problem. Jeśli liczba wierszy, których dotyczy problem, wynosi 1, oznacza to, że udało się w inny sposób (jeśli jest to 0), doszło do kolizji i druga strona wygrała.
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);
To jest przykład blokowania optymistycznego uzyskanego bez transakcji i za pomocą jednej operacji SQL.
Jak napisano, problem polega na tym, że w slot
musi być już co najmniej jeden wiersz tabeli, aby działał (w przeciwnym razie klauzula SELECT zawsze zwróci pusty zestaw rekordów i w takim przypadku nic nie zostanie wstawione, jeśli nie ma kolizji. Istnieją dwie możliwości, aby faktycznie działał:
- wstaw jeden fikcyjny wiersz w tabeli, może z datą z przeszłości
-
przepisz tak, aby główna klauzula FROM odnosiła się do dowolnej tabeli, która ma co najmniej jeden wiersz lub lepiej, stwórz jedną małą tabelę (może nazwać
dummy
) z tylko jedną kolumną i tylko jednym rekordem i przepisz w następujący sposób (zauważ, że nie ma już potrzeby stosowania klauzuli 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);
Oto seria instrukcji, które po prostu skopiuj/wklej, pokazują pomysł w działaniu. Założyłem, że kodujesz datę/czas w polach int jako liczbę z połączonymi cyframi daty i godziny.
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`;
Jest to oczywiście skrajny przykład blokowania optymistycznego, ale w końcu jest bardzo wydajny, ponieważ wszystko odbywa się za pomocą tylko jednej instrukcji SQL i przy niewielkiej interakcji (wymiany danych) między serwerem bazy danych a kodem php. Co więcej, praktycznie nie ma „prawdziwego” blokowania.
...lub z blokowaniem pesymistycznym
Ten sam kod może stać się dobrą implementacją Pessimistc Locking, która jest otoczona wyraźnymi instrukcjami blokowania/odblokowywania tabeli:
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;
Oczywiście w tym przypadku (blokada pesymistyczna) SELECT i INSERT mogą być rozdzielone i jakiś kod php wykonywany pomiędzy. Jednak ten kod pozostaje bardzo szybki do wykonania (brak wymiany danych z php, brak pośredniego kodu php), więc czas trwania blokady pesymistycznej jest najkrótszy z możliwych. Utrzymywanie jak najkrótszej blokady pesymistycznej jest kluczowym punktem, aby uniknąć spowolnienia aplikacji.
W każdym razie musisz sprawdzić liczbę zwracanych rekordów, których dotyczy problem, aby wiedzieć, czy się udało, ponieważ kod jest praktycznie taki sam, a więc otrzymujesz informacje o sukcesie/porażce w ten sam sposób.
Tutaj http://dev.mysql.com/doc/ refman/5.0/en/insert-select.html mówią, że "MySQL nie zezwala na jednoczesne wstawianie instrukcji INSERT... SELECT" więc nie powinna być potrzebna blokada pesymistyczna, ale i tak może to być dobra opcja, jeśli uważasz, że zmieni się to w przyszłych wersjach MySQL.
Jestem „optymistą” że to się nie zmieni;-)