Wat je nodig hebt is vergrendelen . Transacties zijn inderdaad "niet strikt noodzakelijk".
U kunt kiezen tussen "pessimistische vergrendeling" en "optimistische vergrendeling". De beslissing over welke van deze twee mogelijkheden is aan u en moet in principe worden beoordeeld rekening houdend met:
- het niveau van gelijktijdigheid dat je hebt
- de duur van de atomaire bewerkingen in de database
- de complexiteit van de hele operatie
Ik zal aanbevelen om deze twee te lezen om een idee te krijgen van de betrokken zaken:
- Optimistisch versus pessimistisch vergrendelen
- Optimistische vergrendeling in MySQL (hier enkele voorbeelden die laten zien hoe transacties niet strikt noodzakelijk zijn)
Een voorbeeld om beter uit te leggen
Dit is misschien niet zo elegant, maar het is slechts een voorbeeld dat laat zien hoe het mogelijk is om alles te doen zonder transactie (en zelfs zonder de UNIEKE beperkingen). Wat u moet doen, is de volgende gecombineerde INSERT + SELECT-statemet gebruiken en na uitvoering om het aantal betrokken rijen te controleren. Als het aantal betrokken rijen 1 is, is het anders gelukt (als het 0 is), is er een botsing geweest en heeft de andere partij gewonnen.
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);
Dit is een voorbeeld van Optimistic Locking verkregen zonder transacties en met een enkele SQL-bewerking.
Zoals het is geschreven, heeft het het probleem dat er al minstens één rij in het slot
moet zijn tabel om het te laten werken (anders zal de SELECT-component altijd een lege recordset retourneren en in dat geval wordt er niets ingevoegd, zelfs als er geen botsingen zijn. Er zijn twee mogelijkheden om het echt te laten werken:
- voeg een dummy-rij in de tabel in, misschien met de datum in het verleden
-
herschrijf zodat de hoofdcomponent FROM verwijst naar elke tabel die ten minste één rij heeft of beter, maak één kleine tabel (misschien met de naam
dummy
) met slechts één kolom en slechts één record erin en herschrijf als volgt (merk op dat de GROUP BY-component niet langer nodig is)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);
Hier volgt een reeks instructies die, als u eenvoudig kopieert/plakt, het idee in actie laat zien. Ik heb aangenomen dat u datum/tijden in int-velden codeert als een getal met de cijfers van datum en tijd aaneengeschakeld.
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`;
Dit is duidelijk een extreem voorbeeld van Optimistic Locking, maar is uiteindelijk zeer efficiënt omdat alles wordt gedaan met slechts één SQL-instructie en met weinig interactie (gegevensuitwisseling) tussen de databaseserver en php-code. Verder is er praktisch geen "echte" vergrendeling.
...of met pessimistische vergrendeling
Dezelfde code kan een goede Pessimistc Locking-implementatie worden, alleen omringd door expliciete instructies voor het vergrendelen/ontgrendelen van tabellen:
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;
Natuurlijk kunnen in dit geval (pessimistische vergrendeling) de SELECT en INSERT worden gescheiden en wat php-code daartussen worden uitgevoerd. Deze code blijft echter zeer snel uit te voeren (geen gegevensuitwisseling met php, geen tussenliggende php-code) en dus is de duur van de Pessimistic Lock zo kort mogelijk. Het zo kort mogelijk houden van Pessimistic Lock is een belangrijk punt om vertraging van de applicatie te voorkomen.
Hoe dan ook, u moet het aantal geretourneerde records van de getroffen records controleren om te weten of het is gelukt, aangezien de code praktisch hetzelfde is en u de informatie over succes/mislukking op dezelfde manier krijgt.
Hier http://dev.mysql.com/doc/ refman/5.0/en/insert-select.html ze zeggen dat "MySQL gelijktijdige invoegingen voor INSERT ... SELECT-statements niet toestaat" dus het zou niet nodig moeten zijn de Pessimistic Lock, maar dit kan hoe dan ook een goede optie zijn als je denkt dat dit in toekomstige versies van MySQL zal veranderen.
Ik ben "Optimistisch" dat dit niet zal veranderen;-)