事务的四个特性(ACID)
- 原子性(Atomicity):原子性指整个数据库事务是不可分割的工作单位。事务中所有的数据库操作,要么全部提交成功,要么全部失败回滚。
- 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。
- 隔离性(Isolation):事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见。
- 持久性(Durability):事务一旦提交,其结果是永久性的,即使发生宕机等故障,数据库也能将数据恢复。
Innodb 的事务隔离级别
事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读。下面通过事例一一阐述它们的概念与联系。
- 读未提交(Read uncommitted): 一个事务可以读取另一个未提交事务的数据。会出现脏读、不可重复读、幻读。
- 读已提交(Read committed):一个事务可以读取到另一个事务提交后的数据;会出现不可重复读、幻读
- 可重复读(Repeatable read):Innodb的默认隔离级别,保证在同一个事务中多次读取同样数据的结果是一样的。MVCC机制让数据变得可重复读。可能会出现幻读
- 串行化(Serializable):是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
- 每种隔离级别对应的问题
幻读
当前事务读取到其它事务新插入的数据,幻读专指新插入的数据,在可重复读隔离级别下,只有当前读可能存在幻读
示例表
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);
- T1时刻,查询到的结果(5,5,5),在T2时刻id=0的记录d值被设置为5,所以T3时刻读到了数据(0,0,5),(5,5,5) ;此时就出现了幻读
幻读有什么问题
语义上破坏了加锁的意义
- T1时刻对d=5的这一行加了锁,但是没有给id=0的这一行加锁,这样T2时刻,两条SQL都可以顺利执行
- 加锁的本意是将d=5的所有行都锁住,现在的情况是没有锁定新出现的d=5的行,破坏了加锁的语义
数据一致性
我们知道,锁的设计是为了保证数据的一致性。而这个一致性,不止是数据库内部数据状
态在此刻的一致性,还包含了数据和日志在逻辑上的一致性
- 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),出现了严重的数据不一致问题
sql回放
-
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)/
- 依然不能解决幻读问题
间隙锁【GAP-LOCK】
行锁不能解决幻读问题,需要引入新的锁,间隙锁
当执行 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]。
加锁的规则
- 加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”。
- 原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭 区间。
- 原则 2:查找过程中访问到的对象才会加锁。
- 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
- 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
- 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
总结
Innodb 通过引入间隙锁,解决了幻读的问题,也带来了并发度降低的问题,但是保证了数据的一致性。通过将确定的间隙加锁,进而实现不能随意插入,解决了幻读问题
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/83658.html