When the condition is met during the race, but you must be careful how you check who won the race.
Consider the following demonstration of how this works and why you should be careful.
First set up some minimal tables.
CREATE TABLE table1 ( `id` TINYINT UNSIGNED NOT NULL PRIMARY KEY, `locked` TINYINT UNSIGNED NOT NULL, `updated_by_connection_id` TINYINT UNSIGNED DEFAULT NULL ) ENGINE = InnoDB; CREATE TABLE table2 ( `id` TINYINT UNSIGNED NOT NULL PRIMARY KEY ) ENGINE = InnoDB; INSERT INTO table1 (`id`,`locked`) VALUES (1,0);
id plays the role of id in your table, updated_by_connection_id acts as assignedPhone and locked as reservationCompleted .
Now let's start the race test. You should open 2 command line / terminal windows, connect to mysql and use the database in which you created these tables.
Compound 1
start transaction;
Compound 2
start transaction;
Compound 1
UPDATE table1 SET locked = 1, updated_by_connection_id = 1 WHERE id = 1 AND locked = 0;
Query OK, 1 line affected (0.00 sec.) Matching lines: 1 Changed: 1 Warnings: 0
Compound 2
UPDATE table1 SET locked = 1, updated_by_connection_id = 2 WHERE id = 1 AND locked = 0;
Compound 2 is now waiting
Compound 1
SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 1 | 1 | +----+--------+--------------------------+
commit;
At this point, connection 2 is freed up to continue and displays the following:
Compound 2
Request OK, 0 rows affected (23.25 seconds) Matching rows: 0 Changed: 0 Warnings: 0
SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 1 | 1 | +----+--------+--------------------------+
commit;
Everything looks great. We see that yes, the WHERE clause has been respected by race.
The reason I said that you need to be careful is because things are not so simple in a real application. You may have other actions happening inside the transaction, and this can actually change the results.
Let reset the database with the following:
delete from table1; INSERT INTO table1 (`id`,`locked`) VALUES (1,0);
And now consider this situation when SELECT is executed before UPDATE.
Compound 1
start transaction; SELECT * FROM table2;
Empty set (0.00 s)
Compound 2
start transaction; SELECT * FROM table2;
Empty set (0.00 s)
Compound 1
UPDATE table1 SET locked = 1, updated_by_connection_id = 1 WHERE id = 1 AND locked = 0;
Query OK, 1 line affected (0.00 sec.) Matching lines: 1 Changed: 1 Warnings: 0
Compound 2
UPDATE table1 SET locked = 1, updated_by_connection_id = 2 WHERE id = 1 AND locked = 0;
Compound 2 is now waiting
Compound 1
SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 1 | 1 | +----+--------+--------------------------+ 1 row in set (0.00 sec)
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 1 | 1 | +----+--------+--------------------------+ 1 row in set (0.00 sec)
commit;
At this point, connection 2 is freed up to continue and displays the following:
Request OK, 0 rows affected (20.47 sec.) Matching rows: 0 Changed: 0 Warnings: 0
Ok, let's see who won:
Compound 2
SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 0 | NULL | +----+--------+--------------------------+
Wait what? Why is locked 0 and updated_by_connection_id NULL ??
This is a cautious mention. The culprit is actually related to the fact that we made a choice at the beginning. To get the correct result, we can run the following:
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 1 | 1 | +----+--------+--------------------------+
commit;
Using SELECT ... FOR UPDATE, we get the correct result. This can be very confusing (as it was for me, initially), since SELECT and SELECT ... FOR UPDATE give two different results.
The reason for this is due to the default isolation level of READ-REPEATABLE . When the first SELECT is executed, immediately after the start transaction; snapshot is taken. All future inactive readings will be made from this snapshot.
Therefore, if you are just naively SELECT after performing the update, it will pull the information from this initial snapshot, which before , this row has been updated. By doing SELECT ... FOR UPDATE, you force it to receive the correct information.
However, again, in a real application this can be a problem. Say, for example, your request is wrapped in a transaction, and after the update, you want to display some information. The collection and output of this information can be done using a separate reusable code that you DO NOT want to put with FOR UPDATE clauses just in case. This would lead to great disappointment due to unnecessary blocking.
Instead, you will want to select a different track. You have many options here.
First, make sure you complete the transaction after completing UPDATE. In most cases, this is probably the best, easiest choice.
Another option is not to try to use SELECT to determine the result. Instead, you can read the affected lines and use this (1-line update updated to 0 lines) to determine if UPDATE was successful.
Another option, and one that I often use, since I want a single request (for example, an HTTP request) to be completely completed in one transaction, is to make sure that the first statement executed in the transaction is either UPDATE or SELECT. .. FOR UPDATE . This will result in the picture being NOT accepted until the connection is continued.
Repeat reset our test database and see how it works.
delete from table1; INSERT INTO table1 (`id`,`locked`) VALUES (1,0);
Compound 1
start transaction; SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 0 | NULL | +----+--------+--------------------------+
Compound 2
start transaction; SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
Compound 2 is now pending.
Compound 1
UPDATE table1 SET locked = 1, updated_by_connection_id = 1 WHERE id = 1 AND locked = 0;
Query OK, 1 row affected (0.01 sec.) Matching rows: 1 Changed: 1 Warnings: 0
SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 1 | 1 | +----+--------+--------------------------+
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 1 | 1 | +----+--------+--------------------------+
commit;
Compound 2 is now released.
Compound 2
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 1 | 1 | +----+--------+--------------------------+
Here you can really have server-side code to check the results of this SELECT and know what it is for sure, and not even continue the next steps. But, for completeness, I will finish as before.
UPDATE table1 SET locked = 1, updated_by_connection_id = 2 WHERE id = 1 AND locked = 0;
Query OK, 0 rows affected (0.00 sec.) Matching rows: 0 Changed: 0 Warnings: 0
SELECT * FROM table1 WHERE id = 1;
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 1 | 1 | +----+--------+--------------------------+
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
+----+--------+--------------------------+ | id | locked | updated_by_connection_id | +----+--------+--------------------------+ | 1 | 1 | 1 | +----+--------+--------------------------+
commit;
Now you can see that in Connection 2 SELECT and SELECT ... FOR UPDATE give the same result. This is because the snapshot that SELECT reads was not created until connection 1 was committed.
So, back to the original question: Yes, the WHERE clause is checked by the UPDATE statement in all cases. However, you should be careful with any SELECT you can do to avoid incorrectly defining the result of this UPDATE.
(Yes, another option is to change the transaction isolation level. However, I have no experience with this and any of them that may exist, so I won’t go into it.)