1.什么是事务
在数据库中,多条SQL执行语句(主要包含insert、update、delete)共同执行。其中某条发生了错误,前面执行过的SQL语句会回滚,后面的SQL执行语句不会执行,还原到执行前的数据。这样的一个执行过程被称为事务。
事务主要是为了确保数据的完整性,一切正常就全部执行,中间发生了错误就全部不执行。
MySQL中只有InnerDB数据引擎支持事务。
2.事务四大特性
事务具有ACID四大特性
- A(Atomicity):原子性。事务中的工作单元划分到原子级别(各个SQL语句)。要么全部执行,要么全部不执行,不会在中间某个单元结束执行。例:扫码支付后,要么支付成功,要么支付失败。不会出现本人支付成功、商家没有收到钱情况。
- C(Consistency):一致性。事务开始前和事务开始后,数据关联是一致的,数据完整性没有被破话。例:扫码支付100元,本人账户必定扣除100元,商家账户必定新增100元。
- I(Isolation):隔离性。多个事务并发执行时,避免交叉执行导致数据不一致。例:多人同时扫码支付,减少的都是自己的钱,不会影响到其他人的账户余额。
- D(Durability):持久性。事务执行完毕后,对数据的修改是长久性的。不会被修改、回滚回去。例:扫码支付成功后,你的账户减少的钱是一直都会生效的。
3.事务的隔离级别
多个事务同时并发操作同一数据时,数据可能会发生问题。为了解决事务并发导致的数据不一致性的问题,数据库提供了隔离级供我们使用。
并发产生的问题
- 脏读:一个事务读到了另一个未提交事务修改过的数据
- 幻读:一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。
- 不可重复读:一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每次对该数据进行一次修改并提交后,该事务都能查询得到最新值。
隔离级别
- 读未提交(Read Uncommitted):并发下,A事务可以读取到B事务未提交过的数据。容易发生脏读、幻读、不可重复读的线像。一版情况下数据库不会使用该隔离级别。
- 读已提交(Read Committed):并发下,A事务比B事务开始执行,B事务如果要读取自己修改的数据,只能在A事务完成修改后已提交后,才能读取到。解决了脏读的问题,但可能发生幻读和不可重复读现像。
- 可重复读(Repeatable Read):并发下,B事务若想读到自己修改的数据,只能在A事务修改过数据并提交后,自己也提交事务后,才能读取到。及解决了脏堵、幻读的问题,单可能发生不可重复读。
- 可串行化(Serializable):并发下,最为严格的事务级别,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。但是执行效率低下,一般不采用。
隔离级别级别越高对于数据库的性能影响越大:可串行化>可重复读>读已提交>读未提交
MySQL和Spring中默认的事务隔离级别是“读已提交”。
脏读 | 幻读 | 可重复读 | |
---|---|---|---|
读未提交 | 是 | 是 | 是 |
读已提交 | 否 | 是 | 是 |
可重复读 | 否 | 否 | 是 |
可串行化 | 否 | 否 | 否 |
4. SpringBoot中事务的配置
SpringBoot中如果使用事务直接在启动类上加上@EnableTransactionManagement即可。
然后在对应的service实现方法上加上@Transactional即可。
查看@Transactional源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
// 事务执行默认值
@AliasFor("transactionManager")
String value() default "";
// 事务执行默认值
@AliasFor("value")
String transactionManager() default "";
// 事务的传播级别
Propagation propagation() default Propagation.REQUIRED;
// 事务的隔离级别,默认的是读已提交
Isolation isolation() default Isolation.DEFAULT;
// 事务超时时长,-1代表永不超时
int timeout() default -1;
// 是否设置为只读的事务
boolean readOnly() default false;
// 设置需要进行回滚的异常类数组
Class<? extends Throwable>[] rollbackFor() default {};
// 设置需要进行回滚的异常类名称数组
String[] rollbackForClassName() default {};
// 用于设置不需要进行回滚的异常类数组
Class<? extends Throwable>[] noRollbackFor() default {};
// 用于设置不需要进行回滚的异常类名称数组
String[] noRollbackForClassName() default {};
}
这些配置中着重讲一下事务的传播机制和事务的隔离级别,隔离级别由于上文已经说了。这里只讲隔离级别。
事务的传播主要指的是A事务执行中用到了B事务,或者A事务和B事务同时在C事务中等这种事务嵌套的使用。
事务的传播行为主要分为七种。
-
PROPAGATION_REQUIRED: 如果执行时,已经在一个事务中,就加入该事务。否则就开启一个新的事务。是默认的事务传播行为
-
PROPAGATION_SUPPORTS:如果执行时,已经在一个事务中,就加入该事务。没有的话,就不以事务的方式进行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
-
PROPAGATION_MANDATORY:如果执行中,已经在一个事务中,就不能发起自己的事务。如果没有在一个事务中,就抛出异常。
-
PROPAGATION_REQUIRES_NEW:如果执行中,已经在一个事务中,则这个事务将被挂起。并开启一个事务执行当前业务方法。执行完毕后,回复原来挂起的事务继续进行。
-
PROPAGATION_NOT_SUPPORTED :总是非事务地执行,并挂起任何存在的事务。
-
PROPAGATION_NEVER :总是非事务地执行,如果存在一个活动事务,则抛出异常。
-
PROPAGATION_NESTED :如果业务方法在一个既有的事务中执行,则该业务方法将在一个嵌套的事务中进行;否则,按照TransactionDefinition.PROPAGATION_REQUIRED来对待。它使用一个单独的事务,这个事务可以有多个rollback点,内部事务的rollback对外部事务没有影响,但外部事务的rollback会导致内部事务的rollback。这个行为只对DataSourceTransactionManager有效。
5.事务什么情况下会失效
- 1.数据库引擎是否支持事务(MySql的MyIsam引擎不支持事物)
- 2.注解所在的类是否被加载成Bean
- 3.注解所在方法是否为public修饰的
- 4.是否发生了自调用问题
- 5.所用数据源是否加载了事务管理器
- 6.@Transactional的扩展配置propagation是否正确
- 7.异常类型错误、异常被try cath 没有throw