目录
一、事务隔离级别
1.1、事务的四要素(ACID)
1.2、并发所带来的问题
1.3、事务隔离级别类型
1.4、场景复现
1.4.1、脏读
1.4.2、不可重复读
1.4.3、幻读
二、Spring事务的传播机制
原子性:事务的所有操作都是原子性,即要不当前操作全部做完。如果中间操作失败,则回到最初的状态。即要不全做完,要不全不做。
一致性:事务开始前和事务开始后,数据库的完整性约束没有被破坏。
隔离性:同一时间,只允许一个事务操作同一个数据源。
持久性:事务完成后,对数据库的所有操作都会持久化到数据库中,不能回滚。
脏读:事务A读取了事务B更新的数据,然后事务B又进行回滚。此时事务A读取到了脏数据。
不可重复读:事务A多次读取同一数据源出现结果不一致。如:事务B在事务A读取数据源过程中,对数据源进行了更新,导致事务A读取到数据不一致。主要出现在修改场景。
幻读:事务A在更新数据时,事务B又新增了一条新的记录,事务A提交之后发现新增之后的数据没有更新,出现幻读。主要出现新增或者删除等场景。
数据准备:
#表结构
CREATE TABLE `Class_Info` (
`Id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`Name` varchar(11) DEFAULT NULL COMMENT '名称',
`GradeId` int(11) DEFAULT NULL COMMENT '年级id',
`HeadTeacher` varchar(11) DEFAULT NULL COMMENT '班主任',
`AddTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`UpdateTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `Class_Info` (`Id`, `Name`, `GradeId`, `HeadTeacher`, `AddTime`, `UpdateTime`)
VALUES
(1, '李四', 1, '1', '2021-03-23 19:28:44', '2021-03-23 19:28:44');
INSERT INTO `Class_Info` (`Id`, `Name`, `GradeId`, `HeadTeacher`, `AddTime`, `UpdateTime`)
VALUES
(2, '张三', 1, '1', '2021-03-23 19:28:44', '2021-03-23 19:28:44');
(1)#设置会话事务隔离级别为读未提交
SET session TRANSACTION ISOLATION LEVEL Read uncommitted;
(2)#设置事务为不自动提交
set autocommit=0;
读未提交(Read Uncommitted):事务B可以读取事务A为已修改但是未提交的数据。该级别会出现脏读、幻读、不可重复读等现象。
读提交(Read Committed):事务B只能读取事务A提交之后的数据。该场景可出现幻读和不可重复读。不可重复读场景:事务A在B提交之前读取数据,接着在事务B提交之后又读取了数据,发现两次数据不一致。
不可重复读(Repeatable Read):保证一个事务不能读取另一个事务未提交的数据。该级别可能出现幻读场景。
(1)开启事务 A
start transaction;
(2)#修改数据但不提交
update Class_Info set name='王五' where id=1;
(3)#查询语句,发现Name已经变成更新之后的值为 '王五'
select * from Class_Info where id=1;
(4)#回滚修改命令
rollback
(5)#查询语句,发现Name值为更新之前的值 '李四'
select * from Class_Info where id=1;
结果分析:(4)查询语句在还未回滚之前查询到了(1)还未提交的数据,读取到了脏数据。
(1)#开启事务 A
start transaction;
(2)#查询表中所有记录
select * from Class_Info;
(4)开启事务 B
start transaction;
(5)表添加记录
INSERT INTO `Class_Info` (`Id`, `Name`, `GradeId`, `HeadTeacher`, `AddTime`, `UpdateTime`)
VALUES
(3, '李逵', 1, '1', '2021-03-23 19:28:44', '2021-03-23 19:28:44');
(6)提交B事务
commit;
(7)事务接着查询表中所有记录
select * from Class_Info;
(8)提交事务 A
commit;
结果分析:同一个事务中(2)和(7)同一个查询,发现查询的结果不一样,(7)中结果多了一条数据。
(1)#开启事务 A
start transaction;
(2)#将表中所有名字改为 祁东
update Class_Info set name='祁东'
(4)开启事务 B
start transaction;
(5)事务B添加记录
INSERT INTO `Class_Info` (`Id`, `Name`, `GradeId`, `HeadTeacher`, `AddTime`, `UpdateTime`)
VALUES
(3, '李逵', 1, '1', '2021-03-23 19:28:44', '2021-03-23 19:28:44');
(6)提交B事务
commit;
(7)事务接着查询表中所有记录
select * from Class_Info;
(8)提交事务 A
commit;
结果分析:发现结果事务A中本来已经修改所有的名字为祁东,但是由于事务B中间又添加了一条数据,造成出现幻读现象。
PROPAGATION_REQUIRED(默认值):支持当前事务,如果不存在则新建一个事务;如果当前存在事务,则将加入当前事务,合并成一个事务。由于两个操作都在同一个事务中,因此若第二事务出现回滚操作,则整个操作都会回滚。
REQUIRES_NEW:新建事务,如果当前事务存在,则把当前事务挂起;此事务独立提交,即使父级事务异常,子事务还能正常提交。
NESTED:如果当前存在事务,则他变成父事务的子事务,方法结束后不直接提交,而是等事务全部结束后才提交;如果当前没有事务,则新建一个事务;如果异常,父事务可以捕获异常但是不会滚,会正常提交;如果父事务异常,则一定回滚。
SUPPORTS:如果当前存在事务,则加入到当前事务;如果不存在事务则以非事务的方式运行。
NOT_SUPPORTED:以非事务的方式运行;如果当前存在事务,则会把事务挂起;
MANDATORY(强制):如果当前存在事务,则在当前事务中运行;如果当前不存在事务则抛出异常,即父级方法必须有事务。
NEVER:以非事务的方式运行,即当前父级方法必须有事务,如果没有事务则会抛出异常。
总结:方法A为父方法,方法A中执行方法B
正常/异常 | PROPAGATION_REQUIRED | REQUIRES_NEW | NESTED | SUPPORTS | NOT_SUPPORTED | MANDATORY | NEVER |
A正常、B正常 | A提交、B提交 | A提交、B提交 | A提交、B提交 | A提交、B提交 | A提交、B提交 | A提交、B提交 | A提交、B提交 |
A异常、B正常 | A回滚、B回滚 | A回滚、B提交 | A回滚、B回滚 | A回滚、B回滚 | A回滚、B提交 | A回滚、B回滚 | A提交、B提交 |
A异常、B异常 | A回滚、B回滚 | A回滚、B回滚 | A回滚、B回滚 | A回滚、B回滚 | A回滚、B提交 | A回滚、B回滚 | A提交、B提交 |
A正常、B异常 | A回滚、B回滚 | A提交、B回滚 | A提交(捕获异常)、B回滚 A回滚(不捕获异常)、B回滚 |
A回滚、B回滚 | A提交、B提交 | A回滚、B回滚 | A提交、B提交 |