一、事务隔离问题
1、脏读:事务A对某数据进行了修改,但事务还没有提交。这时事务B来读取这个数据,会读到事务A修改后但还没有提交的数据。如果事务A撤销了对该数据的修改,那么事务B就读取到了脏数据,这个就叫脏读。
2、不可重复读:事务A读取一个数据,但事务还没有结束。这时事务B修改了这个数据后提交。事务A再次读取这个数据时,由于事务B修改并提交了这个数据,导致事务A前后两次读到的数据不一致,这个就叫不可重复读。
3、幻读:幻读应该是指事务A读取某个范围的数据,但事务还没有结束。这时事务B插入或删除了某条数据后提交。事务A是看不到事务B已经插入或删除的数据(因为不可重复读),再次插入或删除相同数据会出错,这个就是幻读。幻读的概念很多文章弄混淆了,说的是事务B插入数据后,事务A前后读取的数据不一致,这是不正确的,这叫不可重复读。如果实现了RR级别,是不会出现这种情况的。
二、四种事务隔离级别
1、读未提交 READ-UNCOMMITTED / RU
存在脏读、不可重复读、幻读的问题。
2、读已提交 READ-COMMITTED / RC
解决了脏读的问题,但存在不可重复读、幻读的问题。
3、可重复读 REPEATABLE-READ / RR(默认)
解决了脏读、不可重复读的问题,但存在幻读的问题。是默认隔离级别,使用 MMVC机制实现可重复读。
4、序列化 SERIALIZABLE
解决了脏读、不可重复读、幻读的问题,可保证数据一致性,但各个事务完全串行化执行,性能最低。
事务隔离级别 |
脏读 |
不可重复读 |
幻读 |
读未提交(RU) |
是 |
是 |
是 |
读已提交(RC) |
否 |
是 |
是 |
可重复读(RR) |
否 |
否 |
是 |
串行化(Serializable) |
否 |
否 |
否 |
三、可重复读(RR)幻读问题验证
1、查看隔离级别命令
mysql> show global variables like ‘%isolation%‘;
--------------- -----------------
| Variable_name | Value |
--------------- -----------------
| tx_isolation | REPEATABLE-READ |
--------------- -----------------
1 row in set
或者
mysql> SELECT @@tx_isolation;
-----------------
| @@tx_isolation |
-----------------
| REPEATABLE-READ |
-----------------
1 row in set
2、幻读问题
网上很多文章对幻读问题的解释是不对的。认为幻读问题是某个事务插入数据后导致前面的事务读取的数据记录集不一样。比如,事务A执行两次select操作得到不同的数据集,第一次select得到10条记录,第二次select得到11条记录。这并不是幻读问题,这是不可重复读问题,但在mysql 默认的RR隔离级别是不会出现的。
幻读,并不是说两次读取的结果不一样。幻读侧重的是某一次select 操作后得到的数据无法用于后面的业务操作。比如,select 某记录,发现不存在,准备insert 此记录时,发现此记录已存在,插入报错,此时就是因为发生了幻读。
解决办法,可以在select 时加上X锁,select ... for update 避免幻读问题。事务A在select时加入x锁后,其他事务插入操作需要等待,相当于间接地串行化执行事务。
3、测试验证
有一张user 表保存了用户记录,user表初始状态共3条记录,如下:
mysql> select * from user;
---- ----------- ----- ----- ---------
| id | user_name | age | sex | address |
---- ----------- ----- ----- ---------
| 1 | zhangsan | 20 | 0 | cd |
| 2 | lisi | 21 | 1 | bj |
| 3 | wangwu | 21 | 1 | sh |
---- ----------- ----- ----- ---------
3 rows in set
事务A和事务B分别执行如下操作:
时间 |
事务A |
事务B |
T1 |
Begin; 事务A开始执行 |
|
T1 |
select * from user; 查询结果只有3条记录 |
|
T3 |
|
Begin; 事务B开始执行 |
T4 |
|
insert into user values(4, ‘test‘, 24, 0, ‘tj‘); 事务B插入1条id=4记录 |
T5 |
|
Commit; 事务B提交 |
T6 |
select * from user; 事务A再次查询,结果和上次查询一致,是看不到事务B已经插入的id=4的记录 |
|
T7 |
insert into user values(4, ‘test‘, 24, 0, ‘tj‘); 事务A再次插入id=4的记录报错,由于幻读导致事务A以为不存在id=4的记录,插入后报错 |