本文事务传播行为参考:java - Spring事务传播行为详解_个人文章 - SegmentFault 思否
本文事务隔离级别参考:MySQL的四种事务隔离级别 - 花弄影 - 博客园
一篇文章搞定系列有:
java深拷贝和浅拷贝
java深拷贝和浅拷贝_zhangxiaomin19921的博客-优快云博客
1、事务传播属性
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED—如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,就新建事务
2、关于NESTED、REQUIRED、REQUIRES_NEW的区别
外围事务是REQUIRED的情况下,内部方法也是Nested会加入外围事务作为子事务,内部方法抛出异常也会影响外部事务,从而外部事务,required事务和子事务是不插入的 | |||||||
外围事务是REQUIRED的情况下,内部方法也是Nested会加入外围事务作为子事务,内部方法抛出异常,被cache住了不会影响外部事务,从而外部事务,required事务和子事务是可以插入的 | |||||||
外围事务是REQUIRED的情况下,内部方法也是REQUIRES_NEW不会加入外围事务,内部方法抛出异常,无论是否被cache住都不会影响外部事务,从而REQUIRED是插入的 | |||||||
外围事务是REQUIRED的情况下,内部方法也是REQUIRED会加入外围事务,内部方法抛出异常即使被cache住了也会被外围事务感知,从而这2个REQUIRED都不会插入 |
3、例子
ok表示方法无错误,no表示方法内部抛异常,NESTED、REQUIRED、REQUIRES_NEW表示外部事务或者内部方法的传播行为,结果表示内部方法1,2,3的数据是否可插入,ok表示可插入,no表示不可插入
外围事务是REQUIRED的情况下,内部方法也是Nested会加入外围事务作为子事务,内部方法抛出异常也会影响外部事务,从而外部事务,required事务和子事务是不插入的 | ||||||||
外围事务是REQUIRED的情况下,内部方法也是Nested会加入外围事务作为子事务,内部方法抛出异常,被cache住了不会影响外部事务,从而外部事务,required事务和子事务是可以插入的 | ||||||||
外围事务是REQUIRED的情况下,内部方法也是REQUIRES_NEW不会加入外围事务,内部方法抛出异常,无论是否被cache住都不会影响外部事务,从而REQUIRED是插入的 | ||||||||
外围事务是REQUIRED的情况下,内部方法也是REQUIRED会加入外围事务,内部方法抛出异常即使被cache住了也会被外围事务感知,从而这2个REQUIRED都不会插入 | ||||||||
外围事务 | 方法内事务1 | 方法内事务2 | 方法内事务3 | 步骤4 | 结果 | |||
无 | REQUIRED,ok | REQUIRED,ok | throw RuntimeException | ok,ok | ||||
REQUIRED,ok | REQUIRED,no | ok,no | ||||||
有,REQUIRED | REQUIRED,ok | REQUIRED,ok | throw RuntimeException | no,no | ||||
REQUIRED,ok | REQUIRED,no | no,no | ||||||
REQUIRED,ok | REQUIRED,ok,cache住了 | no,no | ||||||
无 | REQUIRES_NEW,ok | REQUIRES_NEW,ok | throw RuntimeException | Ok, ok | ||||
REQUIRES_NEW,ok | REQUIRES_NEW,no | Ok,no | ||||||
REQUIRED | REQUIRED,ok | REQUIRES_NEW,ok | REQUIRES_NEW,ok | throw RuntimeException | No,ok,ok | |||
REQUIRED,ok | REQUIRES_NEW,ok | REQUIRES_NEW,no | Ok, ok,no | |||||
REQUIRED,ok | REQUIRES_NEW,ok | REQUIRES_NEW,no,异常被捕获 | ok,ok,no | |||||
无 | Nested,ok | Nested,ok | throw RuntimeException | ok,ok | ||||
Nested,ok | Nested,no | ok,no | ||||||
有 | Nested,ok | Nested,ok | throw RuntimeException | no,no | ||||
Nested,ok | Nested,no | no,no | ||||||
Nested,ok | Nested,no,异常被捕获 | ok,no | ||||||
4、事务隔离级别
读未提交,会引起脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
读已提交,会引起不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致
可重复读,会引起幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
串行化:读写数据都会锁住整张表
可以使用 select @@tx_isolation 查看默认事务隔离级别
mysql默认的事务处理级别是可重复读,oracle默认事务隔离级别是读已提交,sqlserver默认事务隔离级别是读已提交
5、事务隔离级别例子:
CREATE TABLE
account
(
id INT,
name VARCHAR(64),
balance INT
)
ENGINE=InnoDB DEFAULT CHARSET=utf8 DEFAULT COLLATE=utf8_general_ci COMMENT='account';
5.1、
1、客户端A打开mysql连接,设置当前会话事务隔离级别是读未提交,查看数据,客户端B打开mysql连接,设置当前会话事务隔离级别是读未提交,更新一条记录,客户端A再次查看数据,发现读取到了客户端B修改了未提交的数据。
客户端A:
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.02 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.02 sec)
mysql> use data;
Database changed
mysql> select * from account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 100 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.03 sec)
mysql>
mysql> select * from account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 800 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.03 sec)
mysql>
客户端B:
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.02 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)
mysql> use data;
Database changed
mysql> UPDATE account set balance=800 where id=1;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> rollback;
Query OK, 0 rows affected (0.03 sec)
mysql>
客户端A查到的数据800就是脏数据,当然,客户端B在回滚后,客户端A可查到正确数据100。
5.2、读已提交
客户端A打开事物,查询表数据,客户端B打开事务,更新一条数据800,这时候客户端B还没有提交,
客户端A再次查询表数据,发现数据没变,解决了脏读的问题。
客户端A:
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.02 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 100 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 100 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql>
客户端B:
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.02 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)
mysql> UPDATE account set balance=800 where id=1;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql>
这时候客户端B提交事务,客户端A再次读表数据,发现查询结果和上次查询结果不一致,出现了不可重复读问题。
客户端A:
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.02 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 100 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 100 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 800 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql>
客户端B:
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.02 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)
mysql> UPDATE account set balance=800 where id=1;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.02 sec)
mysql>
5.3 幻读
客户端A打开连接,首次查询表数据,客户端B打开事务,更新数据,客户端A再次查询,数据一致,没有出现脏读
客户端B提交事务,客户端A再次查询数据,3次数据都一致,没有出现不可重复读。
客户端A:
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.02 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 800 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 800 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 800 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql>
客户端B
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.02 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.02 sec)
mysql> UPDATE account set balance=888 where id=1;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.03 sec)
mysql>
然后再次打开一个事务连接C,然后插入一条数据600,在客户端C查询表数据,有600。在客户端A第四次查询数据,发现没有600这条数据。即使客户端C提交事务后,客户端A还是没有这条600。出现了幻读。
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.02 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT into account VALUE ('4','huandu',600);
Query OK, 1 row affected (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 888 |
| 2 | haixing | 200 |
| 4 | huandu | 600 |
+----+----------+---------+
4 rows in set (0.02 sec)
mysql> commit;
Query OK, 0 rows affected (0.02 sec)
mysql>
客户端的5次查询,数据全部一致,那么如果客户端A做一次更新操作(UPDATE account set balance=balance+1 where id=1;)呢?发现,客户端A虽然显示的数据从始至终都是800,但是做一个更新操作后,数据是889,和数据库真实数据888+1的结果一致,没有出现数据不一致现象。
客户端A:
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.02 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 800 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 800 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 800 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 800 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 800 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql> UPDATE account set balance=balance+1 where id=1;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * from data.account;
+----+----------+---------+
| id | name | balance |
+----+----------+---------+
| 3 | shaofei | 900 |
| 1 | zhangmin | 889 |
| 2 | haixing | 200 |
+----+----------+---------+
3 rows in set (0.02 sec)
mysql>
5.4、可重复读的mvcc机制
可重复读保证多次查询数据一致的机制是。Mvcc机制是怎么保证在可重复读的隔离级别下,select的时候不会出现不可重复读的,又是怎么出现insert插入数据后他不知道的?
InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,一个保存了这个行的创建时间,一个保存的是行的删除时间。这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID)。select操作不会更新事务id,更新操作会更新事务id。
然后要知道mvcc查询和更新的原则:SELECT操作的查询结果要同时满足条件:
1、只会查询版本号小于等于当前版本号的数据作为结果返回,保证了这个数据要么是当前事务修改过的,要么是事务开始之前就已经存在的。
2、行的删除版本要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除
还有mvcc更新事务的机制:
update的更新原则:会生成新的一行,并在原来要修改的列的删除时间列上添加本事务ID。
下面通过几个例子开始解释以上现象:
5.4.1、首先为什么会导致插入数据后他不知道呢?
假设有3个事务,id=1的事务插入3条数据:
start transaction;
insert into yang values(NULL,'yang') ;
insert into yang values(NULL,'long');
insert into yang values(NULL,'fei');
commit;
id=2的事务查询2次表数据:
start transaction;
select * from yang; //(1)
select * from yang; //(2)
commit;
id=3的事务插入一条数据
start transaction;
insert into yang values(NULL,'tian');
commit;
假设在执行这个事务ID为2的过程中,刚执行到(1),这时,有另一个事务ID为3往这个表里插入了一条数据事务,这时候数据库初始数据是:
然后接着执行事务2中的(2),由于id=4的数据的创建时间(事务ID为3),执行当前事务的ID为2,而InnoDB只会查找事务ID小于等于当前事务ID的数据行,所以id=4的数据行并不会在执行事务2中的(2)被检索出来。也就是为什么新增了数据我却不知道了?
5.3.2、删除机制 delete :
假设在执行这个事务ID为2的过程中,刚执行到(1),假设事务执行完事务3插入一条数据我不知道后,接着又执行了事务4;
第四个事务:
start transaction;
delete from yang where id=1;
commit;
执行事务4之后的数据库更新了删除事务id;数据库状态为:
接着执行事务ID为2的事务(2),根据SELECT 检索条件可以知道,它会检索创建时间(创建事务的ID)小于当前事务ID的行和删除时间(删除事务的ID)大于当前事务的行。因此1,2,3都会进入select搜索结果。
5.4.5、 更新机制
假设在执行完事务2的(1)后又执行,其它用户执行了事务3,4,这时,又有一个事务5对这张表执行了UPDATE操作:
start transaction;
update yang set name='Long' where id=2;
commit;
update的更新原则:会生成新的一行,并在原来要修改的列的删除时间列上添加本事务ID,初始状态上图,更新后数据库状态如下:
继续执行事务2的(2),根据select 语句的检索条件,创建时间是5的最新数据不会捞取,捞取的是创建时间为1的就数据,保证了可重复读。得到下表:
假设事务2的(2)执行完之后,未提交事务前,又执行了一个更新操作,会再次改变版本号,那么数据又回又什么变化呢?
假设1:假设更新的是之前没有更新过的id=3的数据:
update yang set name='newfei' where id=3;
这时候数据库状态为:
根据select的查询规则,会得到 yang long newfei。
假设2:假设1执行之后又执行了一个更新操作,更新的是已经被更新的数据id=2
update yang set name='newlong' where id=2;
之后的数据库状态是:
根据select规则,查处的数据是yang newfei newlong。
结束!有错误的欢迎评论或加qq 241317271指教。