Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MySQL isolation level repeatable reads and atomic increments in updates

The last hours I've studied documentation about the different SQL transaction isolation levels and found out that MySQL uses the Repeatable Read Isolation by default and did some experiments. As far as I understand this, selects in an ongoing transaction should see the same data unless the same transaction does updates to it. I found a non repeatable read while using atomic increments (e.g. update table set age=age+1 where id=1).

My test table consists of two columns id and age with one entry 1, 20.

Running the following commands in 2 session I get a non repeatable read:

Transaction 1                Transaction 2
---------------              -------------------
begin;                       begin;
select * from test;          select * from test;   
+----+-----+                +----+-----+
| id | age |                | id | age |
+----+-----+                +----+-----+
|  1 |  20 |                |  1 |  20 |
+----+-----+                +----+-----+
update test set \           
age=age+1 where id=1;
select * from test;         select * from test;
+----+-----+                +----+-----+
| id | age |                | id | age |
+----+-----+                +----+-----+
|  1 |  21 |                |  1 |  20 |
+----+-----+                +----+-----+
commit;                     
                            select * from test;
                            -- age = 20

                            update test set age=age+1 where id=1;
                            select * from test;
                            -- Expected age=21
                            -- got age=22 => Non-Repeatable Read 

Why does the update use a different value than a select would return? Imagine I would do a select and increment the returned value by one following an update of the row. I would get different results.

like image 983
Sebi2020 Avatar asked Sep 14 '25 15:09

Sebi2020


2 Answers

The UPDATE operation from the connection on the right column blocks until the transaction on the left completes. If you want repeatable reads on both connections, you'll need to use BEGIN / COMMIT on both connections.

like image 156
O. Jones Avatar answered Sep 17 '25 05:09

O. Jones


The proper way to run such code is to use FOR UPDATE on the end of the first SELECT. Without that, you are asking for troubles like you found.

What I think happened is (in the righthand connection):

  • the second SELECT on the right did a "repeatable read" and got only 20.
  • the UPDATE saw that 21 had been committed, so it bumped it to 22.
  • the third SELECT new that you had changed the row, so it reread it, getting 22.
like image 31
Rick James Avatar answered Sep 17 '25 05:09

Rick James