事务满足的四个条件(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

1
set session transaction isolation level 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。