Quello che ti serve è il blocco . Le transazioni sono effettivamente "non strettamente necessarie".
Puoi scegliere tra "blocco pessimistico" e "blocco ottimistico". La decisione su quale di queste due possibilità spetta a te e deve essere valutata sostanzialmente considerando:
- il livello di concorrenza che hai
- la durata delle operazioni obbligatorie sul database
- la complessità dell'intera operazione
Consiglierò di leggere questi due per farsi un'idea delle cose coinvolte:
- Blocco ottimista vs. pessimistico
- Blocco ottimistico in MySQL (qui alcuni esempi che mostrano come le transazioni non siano strettamente necessarie)
Un esempio per spiegare meglio
Questo forse non è così elegante ma è solo un esempio che mostra come sia possibile fare tutto senza transazione (e anche senza i vincoli UNIQUE). Ciò che è necessario fare è utilizzare la seguente istruzione combinata INSERT + SELECT e dopo la sua esecuzione per controllare il numero di righe interessate. Se il numero di righe interessate è 1, allora è andata a buon fine (se è 0) c'è stata una collisione e l'altra parte ha vinto.
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);
Questo è un esempio di Optimistic Locking ottenuto senza transazioni e con una singola operazione SQL.
Come è scritto ha il problema che deve esserci già almeno una riga nello slot
tabella affinché funzioni (altrimenti la clausola SELECT restituirà sempre un recordset vuoto e in tal caso non viene inserito nulla evei se non ci sono collisioni. Esistono due possibilità per farlo funzionare effettivamente:
- inserisci una riga fittizia nella tabella magari con la data nel passato
-
riscrivi in modo che la clausola FROM principale si riferisca a qualsiasi tabella che abbia almeno una riga o meglio crei una piccola tabella (magari denominata
dummy
) con una sola colonna e un solo record al suo interno e riscrivi come segue (notare che non è più necessaria la clausola 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);
Di seguito una serie di istruzioni che se si copia/incolla semplicemente mostra l'idea in azione. Ho presupposto che tu codifichi data/ora nei campi int come un numero con le cifre di data e ora concatenate.
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`;
Questo è chiaramente un esempio estremo di Optimistic Locking, ma alla fine è molto efficiente perché tutto viene eseguito con una sola istruzione SQL e con una bassa interazione (scambio di dati) tra il server del database e il codice php. Inoltre, non esiste praticamente alcun blocco "reale".
...o con blocco pessimistico
Lo stesso codice può diventare una buona implementazione di Pessimistc Locking che circonda semplicemente le istruzioni di blocco/sblocco della tabella esplicite:
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;
Ovviamente in questo caso (blocco pessimistico) SELECT e INSERT potrebbero essere separati e del codice php eseguito nel mezzo. Tuttavia questo codice rimane molto veloce da eseguire (nessuno scambio di dati con php, nessun codice php intermedio) e quindi la durata del Pessimistic Lock è la più breve possibile. Mantenere il blocco pessimistico il più breve possibile è un punto chiave per evitare il rallentamento dell'applicazione.
Ad ogni modo è necessario controllare il numero di record interessati dal valore restituito per sapere se è andato a buon fine poiché il codice è praticamente lo stesso e quindi si ottengono le informazioni di successo/fallimento allo stesso modo.
Qui http://dev.mysql.com/doc/ refman/5.0/en/insert-select.html dicono che "MySQL non consente inserimenti simultanei per le istruzioni INSERT ... SELECT" quindi non dovrebbe essere necessario il blocco pessimistico, ma comunque questa può essere una buona opzione se pensi che questo cambierà nelle versioni future di MySQL.
Sono "Ottimista" che questo non cambierà;-)