事务是逻辑上的一组操作,要么都执行,要么都不执行。

ACID四大特性

  • 原子性(Atomicity): 一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
  • 一致性(Consistency): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
  • 隔离性(Isolation): 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • 持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

    MySQL怎么保证

  • 原子性(Atomicity): undo log(回滚日志)

  • 持久性(Durability):redo log(重做日志)
  • 隔离性(Isolation): 锁机制

上述一起保证了一致性。

Spring事务管理接口

  • PlatformTransactionManager: (平台)事务管理器
  • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
  • TransactionStatus: 事务运行状态

    事务管理器

    Spring并不直接管理事务,而是提供了多种事务管理器(接口是PlatformTransactionManager),各个平台自己实现
    image.png ```java package org.springframework.transaction;

public interface PlatformTransactionManager { //获得事务 TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException; //提交事务 void commit(TransactionStatus var1) throws TransactionException; //回滚事务 void rollback(TransactionStatus var1) throws TransactionException; }

  1. <a name="Yvtj3"></a>
  2. ## TransactionDefinition
  3. 基本的事务属性。
  4. ```java
  5. public interface TransactionDefinition {
  6. // 返回事务的传播行为
  7. int getPropagationBehavior();
  8. // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
  9. int getIsolationLevel();
  10. // 返回事务必须在多少秒内完成
  11. int getTimeout();
  12. //返回事务的名字
  13. String getName();
  14. // 返回是否优化为只读事务。
  15. boolean isReadOnly();
  16. }

1. 事务隔离级别

事务并发问题(隔离性)

  • 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
  • 丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
  • 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
  • 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

    隔离级别

    | 隔离级别 | 脏读 | 丢失修改 | 不可重复读 | 幻读 | 备注 | | —- | —- | —- | —- | —- | —- | | READ_UNCOMMITTED
    | √ | | √ | √ | | | READ_COMMITTED
    允许读取并发事务已经提交的数据 | | | √ | √ | Oracle和Pg默认 | | REPEATABLE_READ
    对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改 | | | | √ | MySQL默认 | | SERIALIZABLE
    所有的事务依次逐个执行 | | | | | |

MySQL是怎么解决幻读问题的?

MySQL是怎么解决幻读问题的?

  • 快照读

查询到的数据都是事务开始时那个状态的数据,插入版本号和删除版本号与事务id比对。
MVCC,undo log提供事务回滚+为MVCC提供老版本的数据

  • 实时读

    SELECT…FOR UPDATE申请排它行锁,加锁区间。
    next-key lock: 在可重复读的事务级别下面,普通的select读的是快照,不存在幻读情况,但是如果加上for update的话,读取是已提交事务数据,gap锁保证for update情况下,不出现幻读。Next-Key Locks是行锁和gap锁的组合。

2. 事务传播行为(为了解决业务层方法之间互相调用的事务问题)

案例:JPA oneToMany设置FetchType.LAZY,要把Jpa的事务加入到外层事务
事务传播行为—结合案例
支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRED(默认): 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

不支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。

    aMethod()调用bMethod(),如果aMethod()发生异常回滚,bMethod()不会跟着回滚,因为 bMethod()开启了独立的事务。但是,如果 bMethod()抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,aMethod()同样也会回滚,因为这个异常被 aMethod()的事务管理机制检测到了。

  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况:

  • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价TransactionDefinition.PROPAGATION_REQUIRED。

    如果外部方法开启事务的话,Propagation.NESTED修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。

3. 事务超时属性(一个事务允许执行的最长时间)

4. 事务只读属性(对事物资源是否执行只读操作)

为什么我一个数据查询操作还要启用事务支持呢?

不加Transactional的话,每条sql会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。 如果你一次执行单条查询语句,则没有必要启用事务支持。

5. 事务回滚规则

默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚。

事务状态:TransactionStatus

  1. var status = PlatformTransactionManager.getTransaction(…);
  2. public interface TransactionStatus{
  3. boolean isNewTransaction(); // 是否是新的事务
  4. boolean hasSavepoint(); // 是否有恢复点
  5. void setRollbackOnly(); // 设置为只回滚
  6. boolean isRollbackOnly(); // 是否为只回滚
  7. boolean isCompleted; // 是否已完成
  8. }

两种方式的事务管理

编程式事务管理

通过 TransactionTemplate或者TransactionManager手动管理事务。

声明式事务管理

  • 基于tx和aop命名空间的xml配置文件 ```xml

  1. - **基于@Transactional注解**
  2. ```xml
  3. <!-- Spring: 声明式事务管理 配置事物的注解方式注入-->
  4. <tx:annotation-driven transaction-manager="transactionManager"/>

SpringBoot 会自动配置一个 DataSourceTransactionManager,我们只需在方法(或者类)加上 @Transactional 注解,就自动纳入 Spring 的事务管理了。

  • 基于代理的,不常用 ```xml
    PROPAGATION_REQUIRED,readOnly PROPAGATION_REQUIRED

<— 配置需要事务管理的bean的代理时,通过parent引用这个配置模板,代码如下:->

```

@Transactional使用注意

原理

@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。
调用被@Transactional 注解的 public 方法的时候,实际调用的是代理类TransactionInterceptor类中的 invoke()方法。
基于动态代理原理,@Transactional 注解只有作用到 public 方法上事务才生效。另外,不推荐在接口上使用。

自调用问题

若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有@Transactional 注解的方法的事务会失效。
does-spring-transactional-attribute-work-on-a-private-method