Това, от което се нуждаете, е заключване . Транзакциите наистина „не са строго необходими“.
Можете да избирате между "песимистично заключване" и "оптимистично заключване". Решението коя от тези две възможности зависи от вас и трябва да бъде оценено основно като се има предвид:
- нивото на едновременност, което имате
- продължителността на атомарните операции, които трябва да бъдат в базата данни
- сложността на цялата операция
Ще препоръчам да прочетете тези две, за да изградите представа за свързаните неща:
- Оптимистично срещу песимистично заключване
- Оптимистично заключване в MySQL (тук някои примери, които показват как транзакциите не са строго необходими)
Пример за по-добро обяснение
Това може би не е толкова елегантно, но е само пример, който показва как е възможно да се направи всичко без транзакция (и дори без УНИКАЛНИ ограничения). Това, което е необходимо да направите, е да използвате следния комбиниран оператор INSERT + SELECT и след неговото изпълнение за да проверите броя на засегнатите редове. Ако броят на засегнатите редове е 1, значи е успешен, иначе (ако е 0) е имало сблъсък и другата страна е спечелила.
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);
Това е пример за оптимистично заключване, получено без транзакции и с една SQL операция.
Както е написано, има проблем, че трябва да има поне един ред вече в slot
таблица, за да работи (в противен случай клаузата SELECT винаги ще връща празен набор от записи и в този случай нищо не се вмъква evei, ако няма сблъсъци. Има две възможности да го накарате действително да работи:
- вмъкнете един фиктивен ред в таблицата, може би с датата в миналото
-
пренапишете, така че основната клауза FROM да се отнася до всяка таблица, която има поне един ред или по-добре създайте една малка таблица (може би с име
dummy
) само с една колона и само един запис в нея и пренапишете както следва (обърнете внимание, че вече няма нужда от клаузата 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);
Ето поредица от инструкции, които, ако просто копирате/поставите, показват идеята в действие. Предполагам, че кодирате дата/час в полета int като число с обединени цифри на дата и час.
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`;
Това очевидно е краен пример за оптимистично заключване, но в крайна сметка е много ефективен, защото всичко се прави само с една SQL инструкция и с ниско взаимодействие (обмен на данни) между сървъра на базата данни и php кода. Освен това практически няма "реално" заключване.
...или с песимистично заключване
Същият код може да се превърне в добра реализация на Pessimistc Locking, просто обграждаща изрични инструкции за заключване/отключване на таблица:
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;
Разбира се, в този случай (песимистично заключване) SELECT и INSERT могат да бъдат разделени и някакъв php код да бъде изпълнен между тях. Този код обаче остава много бърз за изпълнение (без обмен на данни с php, без междинен php код) и така продължителността на Pessimistic Lock е възможно най-кратката. Поддържането на Pessimistic Lock възможно най-кратко е ключов момент, за да се избегне забавянето на приложението.
Във всеки случай трябва да проверите броя на засегнатите записи, връщана стойност, за да разберете дали е успяла, тъй като кодът е практически същият и така получавате информацията за успех/неуспех по същия начин.
Тук http://dev.mysql.com/doc/ refman/5.0/en/insert-select.html те казват, че "MySQL не позволява едновременни вмъквания за изрази INSERT ... SELECT" така че не би трябвало да е необходимо Pessimistic Lock, но така или иначе това може да бъде добър вариант, ако смятате, че това ще се промени в бъдещите версии на MySQL.
Аз съм „Оптимист“ че това няма да се промени;-)