事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读。下面通过事例一一阐述它们的概念与联系。
读未提交(Read uncommitted): 一个事务可以读取另一个未提交事务的数据。会出现脏读、不可重复读、幻读。读已提交(Read committed):一个事务可以读取到另一个事务提交后的数据;会出现不可重复读、幻读可重复读(Repeatable read):Innodb的默认隔离级别,保证在同一个事务中多次读取同样数据的结果是一样的。MVCC机制让数据变得可重复读。可能会出现幻读串行化(Serializable):是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。每种隔离级别对应的问题当前事务读取到其它事务新插入的数据,幻读专指新插入的数据,在可重复读隔离级别下,只有当前读可能存在幻读
我们知道,锁的设计是为了保证数据的一致性。而这个一致性,不止是数据库内部数据状 态在此刻的一致性,还包含了数据和日志在逻辑上的一致性
T1时刻,id=5这一行变成了(5,5,100)T2时刻,id=0,这一行变成了(0,5,5)T4时刻,新插入记录(1,1,5) 看似没什么问题,但是写入bin_log日志的顺序如下T2时刻,sessionB提交事务,写入两条语句T4时刻,sessionC提交事务,写入两条语句T6时刻,sessionA提交事务,写入 update t set d = 100 where d = 5;当bin_log同步到从库或故障恢复时;发现记录变为(5,5,100),(0,5,100),(1,1,100),出现了严重的数据不一致问题update t set d=5 where id=0; /(0,0,5)/
update t set c=5 where id=0; /(0,5,5)/
insert into t values(1,1,5); /(1,1,5)/
update t set c=5 where id=1; /(1,5,5)/
update t set d=100 where d=5;/* 所有 d=5 的行,d 改成 100*/
bin_log sql回放 insert into t values(1,1,5); /(1,1,5)/ update t set c=5 where id=1; /(1,5,5)/ update t set d=100 where d=5;/* 所有 d=5 的行,d 改成 100*/ update t set d=5 where id=0; /(0,0,5)/ update t set c=5 where id=0; /(0,5,5)/
依然不能解决幻读问题行锁不能解决幻读问题,需要引入新的锁,间隙锁 当执行 select * from t where d=5 for update 的时候,就不止是给数据库中已 有的 6 个记录加上了行锁,还同时加了 7 个间隙锁。这样就确保了无法再插入新的记录。
跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操 作。间隙锁之间都不存在冲突关系示例: 这里 session B 并不会被堵住。因为表 t 里并没有 c=7 这个记录,因此 session A 加的是 间隙锁 (5,10)。而 session B 也是在这个间隙加的间隙锁。它们有共同的目标,即:保护 这个间隙,不允许插入值。但,它们之间是不冲突的
间隙锁和行锁合称临键锁( next-key lock),每个 next-key lock 是前开后闭区间。也就是说,我 们的表 t 初始化以后,如果用 select * from t for update 要把整个表所有记录锁起来, 就形成了 7 个 next-key lock,分别是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。Innodb 通过引入间隙锁,解决了幻读的问题,也带来了并发度降低的问题,但是保证了数据的一致性。通过将确定的间隙加锁,进而实现不能随意插入,解决了幻读问题