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)了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是他之前所没有的。

  1. 在一个事务中使用相同的 SQL 两次读取,第二次读取到了其他事务新插入的行。

MySQL 事务 - 图1

2.3 不重复读(重点在于读了别的已提交事务

B修改的数据,事务A前后两次读不一致)

不可重复读是指对于数据库中的某个数据,一个事务范围内的多次查询却返回了不同的结果,这是由于在查询过程中,数据被另外一个事务修改并提交了。
指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改(update) 并事务提交,那么第一个事务在同一个事务中两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读

MySQL 事务 - 图2

3.事务的隔离级别

隔离级别 脏读 不可重复读 幻读
读未提交
READ-UNCOMMITTED
读已提交
READ-COMMITTED
×
可重复读
REPEATABLE-READ
× ×
串行化
SERIALIZABLE
× × ×
读未提交(Read Uncommitted) 最低的隔离级别,会读取到其他事务还未提交的内容,存在脏读。
读已提交(Read Committed) 读取到的内容都是已经提交的,可以解决脏读,但是存在不可重复读。
可重复读(Repeatable Read) 在一个事务中多次读取时看到相同的内容,可以解决不可重复读,但是存在幻读。但是在 InnoDB 中不存在幻读问题,对于快照读,InnoDB 使用 MVCC 解决幻读,对于当前读,InnoDB 通过 gap locks 或 next-key locks 解决幻读。
串行化(Serializable) 最高的隔离级别,串行的执行事务,没有并发事务问题。
  • 演示隔离级别 准备条件

创建一张金额表:

  1. create table account
  2. (
  3. id bigint auto_increment
  4. primary key,
  5. amount bigint null,
  6. name varchar(128) null
  7. );

预先写入一条数据;

id amount name
1 1100 A

3.1 读未提交 Read Uncommitted

3.1.1 读未提交 造成的脏读

步骤一:DataGrip 事务模式设置为 手动,,事务隔离级别设置为 读未提交;

image.png

步骤二:当前表 数据:

image.png

步骤三:console 查询

image.png

步骤四:console 1 操作更新

image.png

步骤五:console 再次查询

image.png
读到了 未提交的变动

步骤六:console 1: 事务回滚

image.png

步骤七:console 再次查询

image.png

3.1.2 读未提交 造成的幻读

上面同样的操作 做一个 insert 语句,读了别人 未提交的 数据,多读,别人一回滚 就发现又变少了。

3.1.3 读未提交造成的不可重复读

很好理解,不可重复读 意思是前后 两次读到的不一致。肯定不一致呀。
都会造成脏读,更何况 不可重复读, console 1 只要修改了,console 就会查询到。怎么能做到重复读 ?

3.2 读已提交 Read Commited

3.2.1 读已提交造成的不可重复读

步骤一: 事务模式设置为 读已提交

image.png
Manual 事务手动提交

步骤二:当前数据:

image.png

步骤三:console 查询

开启了一个事务,不要关闭
image.png

步骤四:console 1 操作更新

在当前事务 更新并 查询 更新后的数据,发现age 变化了!
但此时 事务还没有提交!
image.png

步骤五:console 再次查询

在console 无论重新开启多少次 事务查询,发现还是之前的数据。
image.png

步骤六: 在console 1 中 提交事务

image.png

步骤七: 在console中重新查询

发现 变化了,证明 在 事务一中读取了 事务2 已提交的更新。
image.png

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 解决不可不重复读

步骤一:设置事务隔离级别为 可重复读

手动提交
image.png

步骤二:当前数据

image.png

步骤三:console 查询

开启了查询事务,不要关闭
image.png

步骤四:console 1 操作更新

image.png
image.png

步骤五:console 再次查询

image.png
发现没有变化,实现了可重复读;

步骤六:console 事务提交后再次查询

image.png

3.3.2 无法解决幻读

3.3.2.1 正常避免幻读问题

步骤一:在console 开启一个查询事务

image.png

步骤二:在console 1 中新增一条数据,查询更新后,并提交事务

image.png

步骤三:在console 原事务中再次查询

image.png
发现数据没有变化,不会产生幻读(即多数据)

步骤四:在console 中将原事务提交后 重新查询

image.png
发现了新增的数据

3.3.2.2 无法避免幻读的问题

按照3.3.2.1 正常处理的流程,在 console 1 中新增一条数据后并提交

  1. insert student value (12345,'aaa',2);

在console 原事务中查询 是看不到新增数据的。
但是此时 做update 操作

  1. update student set age =10000 where id=12345
  2. id=12345,为新增数据

image.png
然后再次执行查询就会发现 新增的数据 出现了,因此在同一个事务中可重复读 的事务隔离级别发生了幻读。
因为

原因:

因为 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 避免幻读

详见 MySQL 锁机制

5.InnoDB 是如何实现事务的

image.png
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