In MySQL, SELECT FOR UPDATE
is used to prevent lost update and write skew (see more about these problems my answer to 'Lost update' vs 'Write skew'). Note: in MySQL, only SERIALIZABLE
transaction isolation level prevents those problems without SELECT FOR UPDATE
but deadlock error can occur even in that case, so we should use SELECT FOR UPDATE
anyway.
Below I show lost update and write skew and how to prevent them with SELECT FOR UPDATE
with the REPEATABLE READ
isolation level.
First, this is the example of lost update below. There is product
table with id
, name
and stock
as shown below.
product
table:
id |
name |
stock |
1 |
Apple |
10 |
2 |
Orange |
20 |
These steps below shows lost update without SELECT FOR UPDATE
in MySQL. *The stocks of the products decrease when customers buy the products:
Flow |
Transaction 1 (T1) |
Transaction 2 (T2) |
Explanation |
Step 1 |
BEGIN; |
|
T1 starts. |
Step 2 |
|
BEGIN; |
T2 starts. |
Step 3 |
SELECT stock FROM product WHERE id = 2;
20 |
|
T1 reads 20 which is updated later to 13 because a customer buys 7 oranges. |
Step 4 |
|
SELECT stock FROM product WHERE id = 2;
20 |
T2 reads 20 which is updated later to 16 because a customer buys 4 oranges. |
Step 5 |
UPDATE product SET stock = '13' WHERE id = 2; |
|
T1 updates 20 to 13 . |
Step 6 |
COMMIT; |
|
T1 commits. |
Step 7 |
|
UPDATE product SET stock = '16' WHERE id = 2; |
T2 updates 13 to 16 after T1 commits. |
Step 8 |
|
COMMIT; |
T2 commits.
*Lost update occurs. |
Next, these steps below shows how to prevent lost update with SELECT FOR UPDATE
on MySQL. *The stocks of the products decrease when customers buy the products:
Flow |
Transaction 1 (T1) |
Transaction 2 (T2) |
Explanation |
Step 1 |
BEGIN; |
|
T1 starts. |
Step 2 |
|
BEGIN; |
T2 starts. |
Step 3 |
SELECT stock FROM product WHERE id = 2 FOR UPDATE;
20 |
|
With "SELECT FOR UPDATE", T1 reads 20 which is updated later to 13 because a customer buys 7 oranges. |
Step 4 |
|
SELECT stock FROM product WHERE id = 2 FOR UPDATE; |
T2 needs to wait for T1 to commit to read stock with "SELECT FOR UPDATE". |
Step 5 |
UPDATE product SET stock = '13' WHERE id = 2; |
Waiting... |
T1 updates 20 to 13 . |
Step 6 |
COMMIT; |
Waiting... |
T1 commits. |
Step 7 |
|
SELECT stock FROM product WHERE id = 2 FOR UPDATE;
13 |
Now with "SELECT FOR UPDATE", T2 reads 13 which is updated later to 9 because a customer buys 4 oranges. |
Step 8 |
|
UPDATE product SET stock = '9' WHERE id = 2; |
T2 updates 13 to 9 after T1 commits. |
Step 9 |
|
COMMIT; |
T2 commits.
*Lost update doesn't occur. |
Second, this is the example of write skew. There is event
table with name
and user
as shown below.
event
table:
name |
user |
Make Sushi |
John |
Make Sushi |
Tom |
These steps below shows write skew without SELECT FOR UPDATE
in MySQL. *Only 3 users can join the event "Make Sushi":
Flow |
Transaction (JT) |
Transaction (LT) |
Explanation |
Step 1 |
BEGIN; |
|
T1 starts. |
Step 2 |
|
BEGIN; |
T2 starts. |
Step 3 |
SELECT count(*) FROM event WHERE name = 'Make Sushi';
2 |
|
T1 reads 2 so only one user can join it. |
Step 4 |
|
SELECT count(*) FROM event WHERE name = 'Make Sushi';
2 |
T2 reads 2 so only one user can join it. |
Step 5 |
INSERT INTO event values ('Make Sushi', 'Lisa'); |
|
T1 inserts Lisa to event table. |
Step 6 |
COMMIT; |
|
T1 commits. |
Step 7 |
|
INSERT INTO event values ('Make Sushi', 'Kai'); |
T2 inserts Kai to event table. |
Step 8 |
|
COMMIT; |
T2 commits.
4 users join it.*Write skew occurs. |
Next, these steps below shows how to prevent write update on MySQL with SELECT FOR UPDATE
. *Only 3 users can join the event "Make Sushi":
Flow |
Transaction (JT) |
Transaction (LT) |
Explanation |
Step 1 |
BEGIN; |
|
T1 starts. |
Step 2 |
|
BEGIN; |
T2 starts. |
Step 3 |
SELECT count(*) FROM event WHERE name = 'Make Sushi' FOR UPDATE;
2 |
|
With "SELECT FOR UPDATE", T1 reads 2 so only one user can join it. |
Step 4 |
|
SELECT count(*) FROM event WHERE name = 'Make Sushi' FOR UPDATE; |
T2 needs to wait for T1 to commit to read event table with "SELECT FOR UPDATE". |
Step 5 |
INSERT INTO event values ('Make Sushi', 'Lisa'); |
Waiting... |
T1 inserts Lisa to event table. |
Step 6 |
COMMIT; |
Waiting... |
T1 commits. |
Step 7 |
|
SELECT count(*) FROM event WHERE name = 'Make Sushi' FOR UPDATE;
3 |
Now with "SELECT FOR UPDATE", T2 reads 3 so no users can join it. |
Step 8 |
|
COMMIT; |
T2 commits.
3 users join it.*Write skew doesn't occurs. |
select
withoutfor update
) – Poet