一、事务并发会出现的三个问题

数据库事务具有ACID4个特性:

A:Atomic,原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;

C:Consistent,一致性,事务完成后,所有数据的状态都是一致的,即A账户只要减去了100,B账户则必定加上了100;

I:Isolation,隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;

D:Duration,持久性,即事务完成后,对数据库数据的修改被持久化存储。

对于两个并发执行的事务,如果涉及到操作同一条记录的时候,可能会发生问题。因为并发操作会带来数据的不一致性,包括脏读、不可重复读、幻读等。数据库系统提供了隔离级别来让我们有针对性地选择事务的隔离级别,避免数据不一致的问题。

1、Dirty reads--读脏数据。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。

ORDER事务A事务B
1SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2BEGIN;BEGIN;
3UPDATE table SET name="B" where id=1;
4SELECT * FROM table WHERE id = 1;(读到name=B)
5ROLLBACK;
6SELECT * FROM table WHERE id = 1;(读到name=A)
8COMMIT;

2、non-repeatable reads--数据不可重复读。比如事务B中两处读取数据name的值。在第一读的时候,name是A,然后事务A就把name的数据改成B,事务B再读一次,结果就发现,name竟然就变成B了,造成事务B数据混乱。

ORDER事务A事务B
1SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2BEGIN;BEGIN;
3SELECT * FROM table WHERE id = 1;(读到name=A)
4UPDATE table SET name="B" where id=1;
5COMMIT;
6SELECT * FROM table WHERE id = 1;(读到name=B)
8COMMIT;

3、phantom reads--幻象读数据,这个和non-repeatable reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为它始终会读取其他事务提交的数据,但是phantom reads一开始并不会读取到其他事务提交的数据,而是当更新以后才会读取到。 比如事务B尝试读取ID为1的数据,始终都是读不到,但是进行更新操作以后,就可以读取到事务A插入的数据。

ORDER事务A事务B
1SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2BEGIN;BEGIN;
3SELECT * FROM table WHERE id = 1;(无法读取到)
4INSERT INTO table(id, name) VALUES (1, 'A');
5COMMIT;
6SELECT * FROM table WHERE id = 1;(无法读取到)
6UPDATE table SET name = 'B' WHERE id = 1;(更新成功)
7SELECT * FROM table WHERE id = 1;(可以读取到)
8COMMIT;

二、四种隔离级别能解决的三个问题

1、Serializable:避免了一个事务中由于更新操作造成的多次读取数据不一致的问题,避免了幻读,最严格的级别,事务串行执行,资源消耗最大;

2、REPEATABLE READ:保证了一个事务不会读取到已经由另一个事务提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。

3、READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。

4、Read Uncommitted:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。

三、PROPAGATION(spring事务传播机制)

  • PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。也就是说业务方法需要在一个事务中运行,如果 业务方法被调用时,调用业务方法的行为(方法)已经处在一个事务中,那么就加入到该事务,否则为自己创建一个新的事务。 (默认传播机制)

  • PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。也就是说如果业务方法在某个事务范围内被调用, 则该方法成为该事务的一部分。如果业务方法在事务范围外被调用,则该方法在没有事务的环境下执行。

  • PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。也就是说业务方法只能在一个已经存在的事务中执行, 业务方法不能发起自己的事务。如果业务方法在没有事务的环境下被调用,容器就会抛出例外。

  • PROPAGATION_REQUIRESNEW:新建事务,如果当前存在事务,把当前事务挂起。也就是说业务方法被调用时,不管是否已经存在事务, 业务方法总会为自己发起一个新的事务。如果调用业务方法的行为(方法)已经运行在一个事务中,则原有事务会被挂起,新的事务 会被创建,直到业务方法执行结束,新事务才算结束,原先的事务才会恢复执行。

  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起。也就是说业务方法不需要事务。如果 方法没有被关联到一个事务中,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后, 原先的事务便会恢复执行。

  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。也就是说业务方法绝对不能在事务范围内执行。如果业务 方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。

  • PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按REQUIRED属性执行。 它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对 DataSourceTransactionManager事务管理器起效。

另外有一篇文章写的传播机制写的不错,附上链接>>>点击链接

image.png