事务满足的四个条件(ACID):Atomicity(原子性), Consistency(一致性), Isolation(隔离性), Durability(持久性)
事务隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read uncommitted | 不可避免 | 不可避免 | 不可避免 |
read committed | 避免 | 不可避免 | 不可避免 |
repeatable read | 避免 | 避免 | 不可避免 |
serializable | 避免 | 避免 | 避免 |
脏读:读到未提交的新数据。read uncommitted中,事务B更新的数据,A立马在查询中体现,待A处理提交后,B回滚导致A读到的是脏数据。
不可重复读: 数据更新后, 不可读当前事务的版本数据。 read committed中,事务B 提交更新的数据,事务A立马读到事务B提交的版本数据。repeatable read中,事务A始终读到的还是当前事务版本数据。
幻读: 事务两次读到的数据不一样。对于read committed和read uncommitted,事务B提交的更新,事务A在更新前后差到的数据会不一样。对于repeatable read,只有是query和DUI(delete、update、insert)读到的数据不一致,因为query读到的是当前事务的版本数据,DUI读到的是整个数据库最新已提交的版本数据。
serializable 可以避免所有问题的原因:先进入的事物A率先掌握写的权限,所有后进的事务B读正常(在事务A提交之前,读到的只有B事务的快照),但写不可以
【幻读案例】对于repeatable read事务隔离级别,事务A、B正在执行中,事务B插入 id=5的数据并commit。事务A query 自己版本数据发现没有 id=5 的数据,于是插入,但遇到的却是 ERROR 1062 (23000): Duplicate entry '5' for key 'PRIMARY'
,于是事务A再次查询,明明没有 id=5的数据啊,怎么会提示错误呢?难道自己幻读?
当然对于read committed、read uncommitted事务隔离级别,事务A当发现插入错误后,再次查会知道有 id=5的数据,我觉得不应该是幻读,不过也许概念上的幻读可能只是强调两次查询不同吧。
事务A | 事务B |
---|---|
Select * from t 结果:1,2,3,4 |
|
insert into t values(5); commit; |
|
insert into t values(5); 结果:ERROR 1062 (23000): Duplicate entry ‘5’ for key ‘PRIMARY’ |
【解决幻读】Next-Key Lock可以解决幻读问题。InnoDB存储引擎默认的隔离级别是repeatable read,该级别下行锁采用Next-Key Lock。Next-Key Lock对包含记录本身在内的一个范围锁定。比如对上面幻读案例加锁 Select * from t for update
,会对 [1,+∞) 锁定,避免事务B对该范围内的索引更新时导致前后查询不一致,从而避免了幻读。
事务A | 事务B |
---|---|
Select * from t for update 结果:1,2,3,4 |
|
insert into t values(5); 结果: 阻塞 |
|
insert into t values(5); 结果:Query OK, 1 row affected (0.00 sec) |
若将事务隔离级别设为read committed
|
|
级别下行锁采用Record Lock,即只对查询到的记录锁定,达不到避免幻读效果。
事务A | 事务B |
---|---|
Select * from t where a>2 for update 结果:3,4 (Record Lock只对记录3,4加锁) |
|
insert into t values(5); 结果:Query OK, 1 row affected (0.00 sec) |
|
commit; | |
insert into t values(5); 结果:ERROR 1062 (23000): Duplicate entry ‘5’ for key ‘PRIMARY’ |
【为什么serializable隔离级别可以避免幻读】对于serializable隔离级别,一旦事务A更新数据,其他事物的所有操作必须等待事物A提交更新才能对数据库做任何操作。事务A提交更新后,其他事物也会读到最新的版本,包括squery和DUI。