This is to do with isolation levels. If you raise your isolation level to SERIALIZABLE
(the default in MySQL is REPEATABLE READS
) you won't get "phantom reads".
Isolation levels and phantom reads are described on the Wikipedia page for database transaction isolation.
If I run this through as you did, but with the higher isolation level, I get the result you were expecting.
Session 1
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE `ajax`.`zipcodes` (
-> `ZIPCODE` varchar(5) NOT NULL,
-> `CITY` varchar(50) DEFAULT NULL,
-> `STATE` varchar(2) DEFAULT NULL,
-> PRIMARY KEY (`ZIPCODE`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.07 sec)
mysql> insert into ajax.zipcodes values(5, 'Wil', 'AK');
Query OK, 1 row affected (0.00 sec)
Session 2
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Query OK, 0 rows affected (0.00 sec)
mysql> update ajax.zipcodes set city='Dublin' where zipcode=5;
Session 1
mysql> commit;
Query OK, 0 rows affected (0.04 sec)
Session 2
/* continued from previous (was frozen) */
Query OK, 1 row affected (7.54 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.04 sec)
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)
Session 1
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)
NB: This doesn't necessarily mean that you should always be using SERIALIZABLE
- there are trade-offs. Most notable is that the database will acquire a range lock when executing a SELECT
and you'll get more locking-based conflicts.
Update - Explicitly Handling Transactions
Since we have autocommit=0;
set in these scripts, we really should handle the transactions explicitly, rather than expecting a START TRANSACTION
- although in most cases, the database behaves as you'd expect if you'd executed START TRANSACTION
.
However, run the original example while explicitly starting and ending all transactions (including those that are just SELECT
, and you get a different result:
Session 1
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE `ajax`.`zipcodes` (
-> `ZIPCODE` varchar(5) NOT NULL,
-> `CITY` varchar(50) DEFAULT NULL,
-> `STATE` varchar(2) DEFAULT NULL,
-> PRIMARY KEY (`ZIPCODE`)
-> ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.07 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into ajax.zipcodes values(5, 'Wil', 'AK');
Query OK, 1 row affected (0.00 sec)
Session 2
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> update ajax.zipcodes set city='Dublin' where zipcode=5;
Session 1
mysql> commit;
Query OK, 0 rows affected (0.04 sec)
Session 2
/* continued from previous (was frozen) */
Query OK, 1 row affected (8.32 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.04 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.04 sec)
Session 1
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from zipcodes;
+---------+--------+-------+
| ZIPCODE | CITY | STATE |
+---------+--------+-------+
| 5 | Dublin | AK |
+---------+--------+-------+
1 row in set (0.00 sec)