1.事务定义及特征
- 事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)
- 一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成
事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同;
1.1 事务的四大特征
原子性(A):事务的最小单位,不可再分。
- 一致性(C):事务要求所有的DML语句操作的时候,必须保证同时成功或者同时失败。
- 隔离性(I) :事务A和事务B之间具备隔离性。
- 持久性(D):是事务的保证,事务终结的标致(内存中的数据持久到硬盘文件中)。
2.事务引发的问题
2.1 脏读(读到了未提交的数据)
指 一个事务读取了另外一个事务未提交的数据。此时如果回滚,前者两次读取同一个SQL获取的数据是不一样的。
【1】A事务首次查询到id=1 的数据为2,A事务未结束, B事务 将id=1的数据修改为3,B事务并未提交。
【2】A事务再次查询id=1的数据,发现数据发生了变化。
2.2 幻读(重点在于多了数据)
在一个事务的两次查询中数据不一致。例如 有一个事务查询了几列数据,而另一个事务却在此时插入(insert)了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是他之前所没有的。
在一个事务中使用相同的 SQL 两次读取,第二次读取到了其他事务新插入的行。
2.3 不重复读(重点在于读了别的已提交事务
B修改的数据,事务A前后两次读不一致)
不可重复读是指对于数据库中的某个数据,一个事务范围内的多次查询却返回了不同的结果,这是由于在查询过程中,数据被另外一个事务修改并提交了。
指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改(update) 并事务提交,那么第一个事务在同一个事务中两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读
3.事务的隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 READ-UNCOMMITTED |
√ | √ | √ |
读已提交 READ-COMMITTED |
× | √ | √ |
可重复读 REPEATABLE-READ |
× | × | √ |
串行化 SERIALIZABLE |
× | × | × |
读未提交(Read Uncommitted) | 最低的隔离级别,会读取到其他事务还未提交的内容,存在脏读。 |
---|---|
读已提交(Read Committed) | 读取到的内容都是已经提交的,可以解决脏读,但是存在不可重复读。 |
可重复读(Repeatable Read) | 在一个事务中多次读取时看到相同的内容,可以解决不可重复读,但是存在幻读。但是在 InnoDB 中不存在幻读问题,对于快照读,InnoDB 使用 MVCC 解决幻读,对于当前读,InnoDB 通过 gap locks 或 next-key locks 解决幻读。 |
串行化(Serializable) | 最高的隔离级别,串行的执行事务,没有并发事务问题。 |
- 演示隔离级别 准备条件
创建一张金额表:
create table account
(
id bigint auto_increment
primary key,
amount bigint null,
name varchar(128) null
);
预先写入一条数据;
id | amount | name |
---|---|---|
1 | 1100 | A |
3.1 读未提交 Read Uncommitted
3.1.1 读未提交 造成的脏读
步骤一:DataGrip 事务模式设置为 手动,,事务隔离级别设置为 读未提交;
步骤二:当前表 数据:
步骤三:console 查询
步骤四:console 1 操作更新
步骤五:console 再次查询
步骤六:console 1: 事务回滚
步骤七:console 再次查询
3.1.2 读未提交 造成的幻读
上面同样的操作 做一个 insert 语句,读了别人 未提交的 数据,多读,别人一回滚 就发现又变少了。
3.1.3 读未提交造成的不可重复读
很好理解,不可重复读 意思是前后 两次读到的不一致。肯定不一致呀。
都会造成脏读,更何况 不可重复读, console 1 只要修改了,console 就会查询到。怎么能做到重复读 ?
3.2 读已提交 Read Commited
3.2.1 读已提交造成的不可重复读
步骤一: 事务模式设置为 读已提交
Manual 事务手动提交
步骤二:当前数据:
步骤三:console 查询
步骤四:console 1 操作更新
在当前事务 更新并 查询 更新后的数据,发现age 变化了!
但此时 事务还没有提交!
步骤五:console 再次查询
在console 无论重新开启多少次 事务查询,发现还是之前的数据。
步骤六: 在console 1 中 提交事务
步骤七: 在console中重新查询
发现 变化了,证明 在 事务一中读取了 事务2 已提交的更新。
3.2.2 读已提交造成的幻读
根据 3.2.1 可以得知 ,console 在 一个事务中 可以 读到 console 1 中已提交的所有变动。
那么在console 中 第一次查询 id >1 的记录有2条,然后再 console 1中新插入一条后并提交事务
再次在console 中查询 id > 1 的记录 就有3条,因此 造成了 幻读。
3.3 可重复读 Repeatable Read
3.3.1 解决不可不重复读
步骤一:设置事务隔离级别为 可重复读
步骤二:当前数据
步骤三:console 查询
开启了查询事务,不要关闭
步骤四:console 1 操作更新
步骤五:console 再次查询
步骤六:console 事务提交后再次查询
3.3.2 无法解决幻读
3.3.2.1 正常避免幻读问题
步骤一:在console 开启一个查询事务
步骤二:在console 1 中新增一条数据,查询更新后,并提交事务
步骤三:在console 原事务中再次查询
步骤四:在console 中将原事务提交后 重新查询
3.3.2.2 无法避免幻读的问题
按照3.3.2.1 正常处理的流程,在 console 1 中新增一条数据后并提交
insert student value (12345,'aaa',2);
在console 原事务中查询 是看不到新增数据的。
但是此时 做update 操作
update student set age =10000 where id=12345
id=12345,为新增数据
然后再次执行查询就会发现 新增的数据 出现了,因此在同一个事务中可重复读 的事务隔离级别发生了幻读。
因为
原因:
因为 MVCC 只能解决 快照读 下的幻读 ;
当执行select操作是innodb默认会执行快照读.
而对于 update 、 insert、delete 都是采用当前读的模式。
在执行这几个操作时会读取最新的版本号记录,写操作后把版本号改为了当前事务的版本号,所以即使是别的事务提交的数据也可以查询到。
3.4 串行化 Serializable
4.如何解决幻读问题?
1,Record Lock:单个行记录上的锁。
2,Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。
3,Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题
4.1 快照读情况下,使用MVCC 避免幻读
4.2 当前读情况下,使用 next-key Lock 避免幻读
5.InnoDB 是如何实现事务的
Innodb 通过Buffer Pool ,Redo Log Buffer , Redo Log,Undo Log 来实现事务,以一个update 语句为例子:
- Innodb 在接收到一个update 语句的时候,会先根据条件找到数据所在的页,并将页缓存到Buffer Pool 中
- 执行update 语句的时候,修改Buffer Pool 中的数据,也就是内存中的数据。
- 针对update 语句在操作bufferpoll时,同时生产一个redo log 对象,并存入Redo Log buffer 中
- 针对update 语句生产undo log 日志,用于事务回滚。
- 如果事务提交之前,那么咋把Redolog 对象对象进行持久化,写入到redo 日志文件中(磁盘中);
- 写binlog
- 定期将Buffer Pool中 脏页刷到磁盘中。(时机:【1】1s一次,可能丢事务【2】事务提交时【3】复合条件)
- 如果事务回滚,利用undo log 日志进行回滚。
[
](https://www.jianshu.com/p/8845ddca3b23)
引用
MVCC 快照读 引发 幻读原因
https://www.jianshu.com/p/bdcf46cdef7c
https://zhuanlan.zhihu.com/p/48269420
https://www.jianshu.com/p/8845ddca3b23