Ce dont vous avez besoin, c'est de verrouiller . Les transactions ne sont en effet "pas strictement nécessaires".
Vous pouvez choisir entre le « verrouillage pessimiste » et le « verrouillage optimiste ». La décision concernant laquelle de ces deux possibilités vous appartient et doit être évaluée en tenant compte essentiellement :
- le niveau de simultanéité dont vous disposez
- la durée des opérations has-to-be-atomic sur la base de données
- la complexité de l'ensemble de l'opération
Je recommanderai de lire ces deux éléments pour se faire une idée des choses impliquées :
- Verrouillage optimiste ou pessimiste
- Verrouillage optimiste dans MySQL (ici quelques exemples qui montrent comment les transactions ne sont pas strictement nécessaires)
Un exemple pour mieux expliquer
Ce n'est peut-être pas si élégant mais ce n'est qu'un exemple qui montre comment il est possible de tout faire sans transaction (et même sans les contraintes UNIQUE). Ce qu'il faut faire, c'est utiliser l'instruction combinée INSERT + SELECT suivante et après son exécution pour vérifier le nombre de lignes affectées. Si le nombre de lignes affectées est 1, cela a réussi sinon (si c'est 0), il y a eu une collision et l'autre partie a gagné.
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);
Ceci est un exemple de verrouillage optimiste obtenu sans transactions et avec une seule opération SQL.
Comme il est écrit, il a le problème qu'il doit y avoir au moins une ligne déjà dans le slot
table pour qu'elle fonctionne (sinon la clause SELECT renverra toujours un jeu d'enregistrements vide et dans ce cas rien n'est inséré même s'il n'y a pas de collisions. Il y a deux possibilités pour que cela fonctionne réellement :
- insérer une ligne fictive dans le tableau, peut-être avec la date dans le passé
-
réécrivez pour que la clause FROM principale se réfère à n'importe quelle table qui a au moins une ligne ou mieux créez une petite table (peut-être nommée
dummy
) avec une seule colonne et un seul enregistrement et réécrivez comme suit (notez qu'il n'y a plus besoin de la clause 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);
Voici une série d'instructions qui, si vous copiez/collez simplement, montrent l'idée en action. J'ai supposé que vous codez la date/heure sur les champs int sous forme de nombre avec les chiffres de la date et de l'heure concaténés.
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`;
Il s'agit clairement d'un exemple extrême de Optimistic Locking mais au final très efficace car tout se fait avec une seule instruction SQL et avec une faible interaction (échange de données) entre le serveur de base de données et le code php. De plus, il n'y a pratiquement pas de "vrai" verrouillage.
...ou avec verrouillage pessimiste
Le même code peut devenir une bonne implémentation Pessimistc Locking juste entouré d'instructions explicites de verrouillage/déverrouillage de table :
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;
Bien sûr, dans ce cas (verrouillage pessimiste), SELECT et INSERT pourraient être séparés et du code php exécuté entre les deux. Cependant ce code reste très rapide à exécuter (pas d'échange de données avec php, pas de code php intermédiaire) et donc la durée du Pessimistic Lock est la plus courte possible. Garder Pessimistic Lock aussi court que possible est un point clé afin d'éviter le ralentissement de l'application.
Quoi qu'il en soit, vous devez vérifier la valeur de retour du nombre d'enregistrements concernés afin de savoir si cela a réussi, car le code est pratiquement le même et vous obtenez donc les informations de réussite/échec de la même manière.
Ici http://dev.mysql.com/doc/ refman/5.0/en/insert-select.html ils disent que "MySQL n'autorise pas les insertions simultanées pour les instructions INSERT ... SELECT" il ne devrait donc pas être nécessaire d'utiliser le verrou pessimiste, mais cela peut être une bonne option si vous pensez que cela changera dans les futures versions de MySQL.
Je suis "Optimiste" que cela ne changera pas;-)