What you need is locking. Transactions are "not strictly needed" indeed.
You can choose between "pessimistic locking" and "optimistic locking".
The decision about which one of this two possibilities is up to you and has to be evaluated basically considering:
- the level of concurrency that you have
- the duration of the has-to-be-atomic operations on the database
- the complexity of the whole operation
I will recommend to read this two to build up an idea of the involved things:
An Example to explain better
This maybe is not so elegant but is only an example that shows how it is possible to do all without transaction (and even without the UNIQUE constraints).
What is needed to do is to use the following combined INSERT + SELECT statemet and after its execution to check the number of affected rows.
If the number of affected rows is 1 then it has succeded otherways (if it is 0) there have been a collision and the other party have won.
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);
This is an example of Optimistic Locking obtained without transactions and with a single SQL operation.
As it is written it has the problem that there needs to be at least one row already in the slot
table in order it to work (otherways the SELECT clause will always return an empty recordset and in that case nothing is inserted evei if there are no collisions. THere are two possibilities to make it actually working:
- insert one dummy row in the table maybe with the date in the past
rewrite so the main FROM clause refers to any table that has at least one row or better create one little table (maybe named dummy
) with only one column and only one record in it and rewrite as following (note that there is no longer need for the GROUP BY clause)
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);
Here following a series of instruction that if you simply copy/paste shows the idea in action. I have assumed that you encode date/times on int fields as a number with the digits of date and time concatenated.
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`;
This is clearly an extreme example of Optimistic Locking but is very efficient in the end because all is done with only one SQL instruction and with low interaction (data exchange) between the database server and php code. Further there is practically no "real" locking.
...or with Pessimistic Locking
The same code can become a good Pessimistc Locking implementation just surrounding with explicit table lock/unlock instructions:
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;
Of course in this case (Pessimistic Locking) the SELECT and INSERT could be separated and some php code executed in-between. However this code remains very quick to execute (no data exchange with php, no intermediate php code) and so the duration of the Pessimistic Lock is the shortest possible. Keeping Pessimistic Lock as short as possible is a key point in order to avoid slowing down of the application.
Anyway you need to check the number of affected records return value in order to know if it succeeded since the code is practically the same and so you get the success/failure information in the same way.
Here http://dev.mysql.com/doc/refman/5.0/en/insert-select.html they say that "MySQL does not permit concurrent inserts for INSERT ... SELECT statements" so it should not be needed the Pessimistic Lock but anyway this can be a good option if you think that this will be changing in future versions of MySQL.
I am "Optimistic" that this will not change ;-)