Vad du behöver är låsning . Transaktioner är verkligen "inte strikt nödvändiga".
Du kan välja mellan "pessimistisk låsning" och "optimistisk låsning". Beslutet om vilken av dessa två möjligheter är upp till dig och måste utvärderas med hänsyn till:
- nivån av samtidighet som du har
- varaktigheten av de måste-att-vara-atomära operationerna på databasen
- komplexiteten i hela operationen
Jag rekommenderar att du läser dessa två för att bygga upp en uppfattning om de inblandade sakerna:
- Optimistisk kontra pessimistisk låsning
- Optimistisk låsning i MySQL (här några exempel som visar hur transaktioner inte är absolut nödvändiga)
Ett exempel för att förklara bättre
Detta kanske inte är så elegant utan är bara ett exempel som visar hur det är möjligt att göra allt utan transaktion (och även utan UNIKA begränsningar). för att kontrollera antalet berörda rader. Om antalet berörda rader är 1 så har det lyckats på annat sätt (om det är 0) har det skett en kollision och den andra parten har vunnit.
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);
Detta är ett exempel på Optimistisk låsning som erhålls utan transaktioner och med en enda SQL-operation.
Som det står skrivet har det problemet att det måste finnas minst en rad redan i slot
tabell för att det ska fungera (annars kommer SELECT-satsen alltid att returnera en tom postuppsättning och i så fall infogas ingenting evei om det inte finns några kollisioner. Det finns två möjligheter att få det att faktiskt fungera:
- infoga en dummy-rad i tabellen kanske med datumet i det förflutna
-
skriv om så att huvuddelen FROM refererar till vilken tabell som helst som har minst en rad eller bättre skapa en liten tabell (kanske heter
dummy
) med endast en kolumn och endast en post i den och skriv om enligt följande (observera att det inte längre behövs för GROUP BY-satsen)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);
Här följer en serie instruktioner som om du bara kopierar/klistrar visar idén i praktiken. Jag har antagit att du kodar datum/tider i int-fält som ett tal med siffrorna för datum och tid sammanlänkade.
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`;
Detta är helt klart ett extremt exempel på Optimistic Locking men är mycket effektivt i slutändan eftersom allt görs med endast en SQL-instruktion och med låg interaktion (datautbyte) mellan databasservern och php-koden. Dessutom finns det praktiskt taget ingen "riktig" låsning.
...eller med pessimistisk låsning
Samma kod kan bli en bra pessimistisk låsningsimplementering som bara omger med explicita instruktioner för tabelllåsning/upplåsning:
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;
Naturligtvis i det här fallet (pessimistisk låsning) kan SELECT och INSERT separeras och någon php-kod exekveras däremellan. Den här koden förblir dock väldigt snabb att exekvera (inget datautbyte med php, ingen mellanliggande php-kod) och därför är varaktigheten av Pessimistic Lock den kortaste möjliga. Att hålla Pessimistic Lock så kort som möjligt är en nyckelpunkt för att undvika att applikationen saktar ner.
Hur som helst måste du kontrollera antalet påverkade poster som returnerar värde för att veta om det lyckades eftersom koden är praktiskt taget densamma och så att du får information om framgång/misslyckande på samma sätt.
Här http://dev.mysql.com/doc/ refman/5.0/en/insert-select.html de säger att "MySQL tillåter inte samtidiga infogningar för INSERT ... SELECT-satser" så det borde inte behövas Pessimistic Lock men hur som helst kan detta vara ett bra alternativ om du tror att detta kommer att förändras i framtida versioner av MySQL.
Jag är "optimistisk" att detta inte kommer att ändras;-)