重新认识事务(Transaction)

为什么要重新认识事务 起初我们在刚接触数据库都学过事务,在项目实践中都有过使用事务。着业务的越来越大,功能逻辑越来越复杂,可能在某一次生产事故后,我们才会静下心来想一想我们真的能用好事务吗?

很多时候,我们对事务的认知还在事务中必须要执行到commit、rollback,甚至是@Transactional一个注解全部解决的层面。

在实际开发中我们对事务的理解停留在“加不加”的问题,而实际上事务是“怎么加、加在哪、加了会有什么影响”的系统工程。

注:本文将从Spring框架角度的进行描述且只聊单体服务

事务Transaction
ACID

事务是数据库操作的最小执行单元*,保证要么全部成功,要么全部回滚,

*需要注意的一点是我们平时单表操作,由于存在数据库的自动提交机制(autocommit=true),其实也是有事务存在的只不过不是显式声明

核心遵循ACID原则:

  • 原子性(Atomicity):操作不可分割,要么全部成功,要么全部失败回滚。
  • 一致性(Consistency):一致性是指事务执行后,数据库从一个合法的状态转换到另一个合法的状态,满足业务规则和数据约束(如主键唯一、外键关联、字段校验等)
  • 隔离性(Isolation):并发事务互不干扰
  • 持久性(Durability):提交后数据永久生效,不丢失
事务传播等级

🔥 高频使用传播等级

  1. REQUIRED(默认等级)
  • 原理:当前存在事务,则加入该事务;当前无事务,则新建一个事务。
  • 特性:内外方法共用同一个事务连接,任意一方抛异常,整个事务全部回滚。
  • 适用场景:常规业务逻辑(增删改)、单业务链嵌套调用,90%场景首选。
  • 避坑:子方法异常会导致父方法一起回滚,无法局部隔离。
  1. REQUIRES_NEW
  • 原理新建独立事务,并挂起当前存在的外部事务;新事务执行完毕后,恢复外部事务。
  • 特性:内外事务完全隔离,拥有独立的数据库连接和生命周期,互不干扰。
  • 适用场景:子业务需独立提交/回滚(如日志记录、消息发送、补偿操作),子方法失败不影响主流程。
  • 避坑:会占用额外数据库连接,大量使用易导致连接池耗尽,严禁长逻辑使用。
  1. NESTED(嵌套事务)
  • 原理:基于数据库**保存点(SavePoint)**实现嵌套,外层事务存在时创建子事务保存点;无事务则等价于REQUIRED。
  • 特性:外层事务回滚 → 内层事务必回滚;内层事务回滚 → 仅回滚到保存点,不影响外层事务。
  • 适用场景:局部失败无需全盘回滚、支持部分回滚的复杂业务(如分步提交、容错处理)。
  • 避坑Spring 的 NESTED 传播行为仅支持 JDBC 事务(DataSourceTransactionManager),在 JTA分布式事务管理器下会退化为 REQUIRED,并非完全不生效

🧊 小众传播等级

  1. SUPPORTS
  • 原理:有事务则加入,无事务则以非事务方式运行
  • 适用场景:纯查询方法,兼顾有事务/无事务环境,配合只读事务提升性能。
  1. NOT_SUPPORTED
  • 原理:始终以非事务方式运行,存在当前事务则挂起。
  • 适用场景:无需事务的耗时操作(如文件读写、大数据量查询),避免长事务占用连接。
  1. MANDATORY
  • 原理:强制要求当前存在事务,无事务则直接抛出异常
  • 适用场景:核心写操作,严禁无事务执行,防止漏加事务导致数据不一致。
  1. NEVER
  • 原理:强制要求当前无事务,存在事务则直接抛出异常
  • 适用场景:严禁参与事务的操作(如缓存预热、静态资源加载),杜绝事务污染。
事务隔离级别和并发问题
隔离级别 脏读(Dirty Read) 不可重复读(Non-repeatable Read) 幻读(Phantom Read) 典型数据库默认 适用场景
读未提交(Read Uncommitted) ✅ 允许 ✅ 允许 ✅ 允许 基本无 极低一致性要求、追求极致性能
读已提交(Read Committed) ❌ 禁止 ✅ 允许 ✅ 允许 Oracle、SQL Server 大多数业务系统,平衡性能与一致性
可重复读(Repeatable Read) ❌ 禁止 ❌ 禁止 ✅ 允许 MySQL InnoDB 默认 金融 / 订单核心业务,强一致性
串行化(Serializable) ❌ 禁止 ❌ 禁止 ❌ 禁止 极少 极高一致性要求,并发性能极差

✅ = 会出现该问题 ❌ = 不会出现该问题

补充:三大并发问题简单解释

  1. 脏读:读到了其他事务未提交的修改数据,对方回滚后数据无效。
  2. 不可重复读:同一事务内,多次查询同一行数据,结果不一致(其他事务update/delete已提交)。
  3. 幻读:同一事务内,多次查询符合条件的记录数,结果不一致(其他事务insert已提交)。
@Transactional原理

了解的事务的基本概念后,我们通过Spring源码,来看下事务的一些细节。

首先通过@EnableTransactionManagement进入TransactionManagementConfigurationSelector 类,这个类中包含了事务的全部内容。

image-20260323113748873

	/**
	 * Returns {@link ProxyTransactionManagementConfiguration} or
	 * {@code AspectJ(Jta)TransactionManagementConfiguration} for {@code PROXY}
	 * and {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()},
	 * respectively.
	 */
	@Override
	protected String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] {AutoProxyRegistrar.class.getName(),
						ProxyTransactionManagementConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {determineTransactionAspectClass()};
			default:
				return null;
		}
	}

默认情况下EnableTransactionManagement

AdviceMode mode() default AdviceMode.PROXY;

所以我们重点关注 AutoProxyRegistrarProxyTransactionManagementConfiguration

其中 AutoProxyRegistrar 是自动代理机制来增强目标对象的功能,和实际事务关系不太大,我们可以先不看。

我们把中心放到 ProxyTransactionManagementConfiguration

这个类定义了三个Bean。这三个Bean做一件事,就是创建一个事务的advisor

先看一个简单的例子,什么是Advisor:

// 增强逻辑
@Component
public class TaskAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("方法调用前: " + invocation.getMethod().getName());
        Object result = invocation.proceed();
        System.out.println("方法调用后: " + invocation.getMethod().getName());
        return result;
    }
}

// 配置类
@Configuration
public class TaskConfig {

    // 创建advisor
    @Bean
    public DefaultPointcutAdvisor taskAdvisor(TaskAdvice taskAdvice) {
        // 创建切点
        AnnotationMatchingPointcut matchingPointcut = AnnotationMatchingPointcut.forMethodAnnotation(Task.class);
        DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
        // 切面
        defaultPointcutAdvisor.setAdvice(taskAdvice);
        defaultPointcutAdvisor.setPointcut(matchingPointcut);
        return defaultPointcutAdvisor;
    }
}

以上代码在服务启动的时候,会根据切点自动对符合条件得到类进行增强。

再来看事务的配置类,就是做了三件事 , 创建一个事务的增强实现,创建了一个事务属性信息(包含切点信息),再根据以上信息创建一个事务Advisor。所以我们先关注TransactionInterceptor

image-20260323165948395

增强细节,如下图,图二就是我们使用了@Transactional的方法的真实执行时的代码。

image-20260323181058325

image-20260323181132368

image-20260323220703546

image-20260323221243147

image-20260323221316626

看上面的源码有什么用呢,从源码中我们能发现,原来@Transactional是这样运作的,我们从中可以发现几个点

  1. 标识了@Transactional的整个方法都是放到事务中进行的,事务的粒度非常大。此处在下文中重点描述
  2. 声明式事务是AOP技术,声明AdvisorBean代理使用事务的类,所以在实例内调用自己的方法不能触发事务(this.xx调用的是自身,而非增强代理对象)
  3. 开启事务时,如果不设置超时时间,则系统默认超时时间为-1,等于没有超时时间
  4. 在提交/回滚的方法中,包含triggerBeforeCommit等生命周期方法。下一段会重点描述。

接下来,先看一下事务生命周期的相关函数,看了之后我们再对上述的几个点所引出的问题做解答。

进阶知识
事务同步管理器 TransactionSynchronizationManager

Spring事务 | Yiwyn’s ~ShenZhi Blog

这里先引用一下自己的文章

首先,我们先知道TransactionSynchronizationManager的作用是什么

// 官方注释
/**
 * Central delegate that manages resources and transaction synchronizations per thread.
 * To be used by resource management code but not by typical application code.
 *
 * <p>Supports one resource per key without overwriting, that is, a resource needs
 * to be removed before a new one can be set for the same key.
 * Supports a list of transaction synchronizations if synchronization is active.
 *
 * <p>Resource management code should check for thread-bound resources, e.g. JDBC
 * Connections or Hibernate Sessions, via {@code getResource}. Such code is
 * normally not supposed to bind resources to threads, as this is the responsibility
 * of transaction managers. A further option is to lazily bind on first use if
 * transaction synchronization is active, for performing transactions that span
 * an arbitrary number of resources.
 *
 * <p>Transaction synchronization must be activated and deactivated by a transaction
 * manager via {@link #initSynchronization()} and {@link #clearSynchronization()}.
 * This is automatically supported by {@link AbstractPlatformTransactionManager},
 * and thus by all standard Spring transaction managers, such as
 * {@link org.springframework.transaction.jta.JtaTransactionManager} and
 * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}.
 *
 * <p>Resource management code should only register synchronizations when this
 * manager is active, which can be checked via {@link #isSynchronizationActive};
 * it should perform immediate resource cleanup else. If transaction synchronization
 * isn't active, there is either no current transaction, or the transaction manager
 * doesn't support transaction synchronization.
 *
 * <p>Synchronization is for example used to always return the same resources
 * within a JTA transaction, e.g. a JDBC Connection or a Hibernate Session for
 * any given DataSource or SessionFactory, respectively.
 *
 * @author Juergen Hoeller
 * @since 02.06.2003
 * @see #isSynchronizationActive
 * @see #registerSynchronization
 * @see TransactionSynchronization
 * @see AbstractPlatformTransactionManager#setTransactionSynchronization
 * @see org.springframework.transaction.jta.JtaTransactionManager
 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
 */

// 中文翻译
/**
* 核心委托类负责管理每个线程的资源及事务同步 
* 供资源管理代码使用而非供一般的应用程序代码使用 
*
* <p>支持一键一资源模式且不允许覆盖若要为同一键设置新资源必须先移除旧资源 
* 若事务同步处于激活状态则支持维护一个事务同步列表 
*
* <p>资源管理代码应通过 {@code getResource} 方法检查是否存在线程绑定的资源例如 JDBC
* 连接或 Hibernate Session)。此类代码通常不应自行将资源绑定到线程上因为这是事务管理器的职责 
* 另一种可选方案是若事务同步处于激活状态则在首次使用时进行延迟绑定”(lazy bind),
* 以便执行跨越任意数量资源的事务操作 
*
* <p>事务同步的激活与去激活操作必须由事务管理器通过 {@link #initSynchronization()} 
* {@link #clearSynchronization()} 方法来执行这一机制已由 {@link AbstractPlatformTransactionManager}
* 自动实现因此所有的标准 Spring 事务管理器例如
* {@link org.springframework.transaction.jta.JtaTransactionManager} 
* {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}均支持此功能 
*
* <p>资源管理代码仅应在当前管理器处于激活状态时注册同步对象可通过 {@link #isSynchronizationActive}
* 进行检查);若管理器未处于激活状态则应立即执行资源清理工作如果事务同步未处于激活状态
* 则意味着当前不存在事务或者当前的事务管理器不支持事务同步功能 
*
* <p>事务同步机制的一个典型应用场景是 JTA 事务的整个生命周期内确保针对给定的
* DataSource  SessionFactory始终返回同一个资源实例例如同一个 JDBC 连接或 Hibernate Session)。 *
* @author Juergen Hoeller
* @since 2003年6月2日
* @see #isSynchronizationActive
* @see #registerSynchronization
* @see TransactionSynchronization
* @see AbstractPlatformTransactionManager#setTransactionSynchronization
* @see org.springframework.transaction.jta.JtaTransactionManager
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
* @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection

简单说,就两件事

存储事务上下文:在当前线程中绑定事务相关的核心信息(比如数据库连接、事务是否激活、事务名称、隔离级别等),让同一线程内的所有操作都能获取到当前事务的上下文。

管理事务同步器:允许注册 TransactionSynchronization 类型的回调对象,这些对象会在事务的关键阶段(提交前、提交后、回滚前、回滚后、事务完成)触发执行

存储事务上下文我们一般不会操作,更多的是管理事务同步的使用,以下是翻译了注释的源代码

/**
 * 事务同步回调的接口。
 * 由 AbstractPlatformTransactionManager 提供支持。
 *
 * <p>TransactionSynchronization 实现类可实现 Ordered 接口来影响其执行顺序。
 * 未实现 Ordered 接口的同步器会被追加到同步链的末尾。
 *
 * <p>Spring 自身执行的系统级同步逻辑会使用特定的顺序值,允许对其执行顺序进行细粒度的控制(如有必要)。
 *
 * <p>从 5.3 版本开始,该接口实现了 {@link Ordered} 接口,以支持声明式控制同步器的执行顺序。
 * 默认的 {@link #getOrder() 执行顺序} 为 {@link Ordered#LOWEST_PRECEDENCE}(最低优先级),
 * 表示晚执行;若需提前执行,返回更小的数值即可。
 *
 * @author Juergen Hoeller
 * @since 02.06.2003
 * @see TransactionSynchronizationManager
 * @see AbstractPlatformTransactionManager
 * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
 */
public interface TransactionSynchronization extends Ordered, Flushable {

    /** 事务正常提交时的完成状态 */
    int STATUS_COMMITTED = 0;

    /** 事务正常回滚时的完成状态 */
    int STATUS_ROLLED_BACK = 1;

    /** 因启发式混合完成或系统错误导致的完成状态 */
    int STATUS_UNKNOWN = 2;


    /**
     * 返回此事务同步器的执行顺序。
     * <p>默认值为 {@link Ordered#LOWEST_PRECEDENCE}(最低优先级)。
     */
    @Override
    default int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

    /**
     * 挂起此同步器。
     * 若管理了相关资源,应将其从 TransactionSynchronizationManager 中解绑。
     * @see TransactionSynchronizationManager#unbindResource
     */
    default void suspend() {
    }

    /**
     * 恢复此同步器。
     * 若管理了相关资源,应将其重新绑定到 TransactionSynchronizationManager。
     * @see TransactionSynchronizationManager#bindResource
     */
    default void resume() {
    }

    /**
     * 将底层会话刷新到数据存储(如适用):
     * 例如 Hibernate/JPA 会话。
     * @see org.springframework.transaction.TransactionStatus#flush()
     */
    @Override
    default void flush() {
    }

    /**
     * 在事务提交前调用(早于 "beforeCompletion")。
     * 例如可将事务性的对象关系映射(O/R Mapping)会话刷新到数据库。
     * <p>此回调<b>不代表</b>事务一定会被提交。即使调用了此方法,后续仍可能触发回滚决策。
     * 该回调的设计目的是执行仅在提交仍有可能发生时才需要的操作,比如将 SQL 语句刷新到数据库。
     * <p>注意:此方法抛出的异常会传播到提交调用方,并导致事务回滚。
     * @param readOnly 事务是否被定义为只读事务
     * @throws RuntimeException 发生错误时抛出;异常会<b>传播到调用方</b>
     * (注意:此处不要抛出 TransactionException 子类!)
     * @see #beforeCompletion
     */
    default void beforeCommit(boolean readOnly) {
    }

    /**
     * 在事务提交/回滚前调用。
     * 可在事务完成<b>前</b>执行资源清理操作。
     * <p>即使 {@code beforeCommit} 抛出异常,此方法仍会在其之后被调用。
     * 该回调允许在事务完成前(无论最终结果如何)关闭资源。
     * @throws RuntimeException 发生错误时抛出;异常会<b>被记录但不传播</b>
     * (注意:此处不要抛出 TransactionException 子类!)
     * @see #beforeCommit
     * @see #afterCompletion
     */
    default void beforeCompletion() {
    }

    /**
     * 在事务提交后调用。可在主事务<b>成功</b>提交后立即执行后续操作。
     * <p>例如可提交那些依赖主事务成功提交的后续操作,如发送确认消息或邮件。
     * <p><b>注意:</b>此时事务已完成提交,但事务资源可能仍处于活跃且可访问状态。
     * 因此,此阶段触发的任何数据访问代码仍会“参与”原始事务,允许执行一些清理操作(后续无提交动作),
     * 除非显式声明需要在独立事务中运行。
     * 因此:<b>对此处调用的任何事务性操作,使用 {@code PROPAGATION_REQUIRES_NEW} 传播属性。</b>
     * @throws RuntimeException 发生错误时抛出;异常会<b>传播到调用方</b>
     * (注意:此处不要抛出 TransactionException 子类!)
     */
    default void afterCommit() {
    }

    /**
     * 在事务提交/回滚后调用。
     * 可在事务完成<b>后</b>执行资源清理操作。
     * <p><b>注意:</b>此时事务已完成提交或回滚,但事务资源可能仍处于活跃且可访问状态。
     * 因此,此阶段触发的任何数据访问代码仍会“参与”原始事务,允许执行一些清理操作(后续无提交动作),
     * 除非显式声明需要在独立事务中运行。
     * 因此:<b>对此处调用的任何事务性操作,使用 {@code PROPAGATION_REQUIRES_NEW} 传播属性。</b>
     * @param status 完成状态,取值为 {@code STATUS_*} 常量
     * @throws RuntimeException 发生错误时抛出;异常会<b>被记录但不传播</b>
     * (注意:此处不要抛出 TransactionException 子类!)
     * @see #STATUS_COMMITTED
     * @see #STATUS_ROLLED_BACK
     * @see #STATUS_UNKNOWN
     * @see #beforeCompletion
     */
    default void afterCompletion(int status) {
    }

}
事务优化

在复杂业务系统中,事务如果设计不当,往往会导致:

  • 长事务
  • 锁竞争
  • 系统吞吐下降
  • 批量任务阻塞
  • 甚至雪崩

因此事务优化通常关注四个核心点:

  1. 事务边界控制
  2. 事务超时设计
  3. 事务隔离级别
  4. 批量操作优化
事务边界控制

坊间有流传这么一个说法:大厂不推荐使用@Transactional注解。

何为其然也?曰:大部分人在写业务功能的时候,一般是上帝Service类,即业务Service中会有大量的业务代码,其中不乏数据库操作、远程调用服务等(一个方法里包含:数据库操作、RPC调用、MQ发送、复杂业务逻辑)。

举个坏🌰:

    /**
     * 查询银行审批状态
     *
     * @param bizSeq 业务号
     */
    @Transactional(rollbackFor = Exception.class)
    public void getBankApprovalResult(String bizSeq) {
        // 先查询到订单
        Order order = orderMapper.queryById(bizSeq);

        // 查询银行审批状态
        BankReponse reponse = bankService.queryResult(order.getBankApplyId);

        // 如果查询失败,直接抛异常
        if (!reponse.isSuccess()) {
            throw new RuntimeException();
        }
        // 如果成功更新订单表
        order.setBankApprovalSts("pass");
        orderMapper.update(order);

        // 发送通知
        notifyService.sendNotify(order);
    }

好🌰:

缩小事务范围

    /**
     * 查询银行审批状态
     *
     * @param bizSeq 业务号
     */
    public void getBankApprovalResult(String bizSeq) {
        // 先查询到订单
        Order order = orderMapper.queryById(bizSeq);

        // 查询银行审批状态
        BankReponse reponse = bankService.queryResult(order.getBankApplyId);

        // 如果查询失败,直接抛异常
        if (!reponse.isSuccess()) {
            throw new RuntimeException();
        }
        transactionTemplate.execute(status -> {
            // 如果成功更新订单表
            order.setBankApprovalSts("pass");
            orderMapper.update(order);
            return null;
        });

        // 发送通知
        notifyService.sendNotify(order);

    }

更好的🌰:

借用DDD的思想中的一个概念,聚合根和领域仓储,我们要做的是把需要一个事务操作的数据库操作作为一个仓储抽象,而事务的范围则是该仓储实现。DDD事务边界

这类实现会将数据库操作和其他操作进行解耦,getBankApprovalResult方法更多是对细分事务的编排(参考领域服务、领域应用服务)可简单参考:领域驱动设计DDD-理论&实际(更新中) | Yiwyn’s ~ShenZhi Blog

ApplicationService(编排)
DomainService(事务)
Repository

code:

    /**
     * 查询银行审批状态
     *
     * @param bizSeq 业务号
     */
    public void getBankApprovalResult(String bizSeq) {
        // 先查询到订单
        Order order = orderMapper.queryById(bizSeq);

        // 查询银行审批状态
        BankReponse reponse = bankService.queryResult(order.getBankApplyId);

        // 如果查询失败,直接抛异常
        if (!reponse.isSuccess()) {
            throw new RuntimeException();
        }
        // 调用订单服务更新订单结果
        orderService.updResult(order);

        // 发送通知
        notifyService.sendNotify(order);

    }


    /**
     * 订单服务
     */
    @Service
    public class OrderService {

        @Transactional(rollbackFor = Exception.class)
        public void updResult(Order order) {
            // 如果成功更新订单表
            order.setBankApprovalSts("pass");
            orderMapper.update(order);
        }
    }

事务超时时间设计

事务连接超时时间一直以为是被大家忽略的问题(几乎在系统中看不到)

扩展:几乎在所有的高可用系统中,任何的链接都应该设置超时时间,超时时间会让系统在规定时间内快速失败从而释放资源。没有超时时间/超时时间设计不合理会导致等待,若是业务简单则问题可能被掩盖,一旦并发上来,系统雪崩也会随之而来。

举个坏🌰:

  • A线程更新业务123方法执行完成后没有正常提交或者回滚(可能是更新后同步别的系统一直等待响应,或者就是逻辑漏洞)

  • 另一个业务操作线程B也要更新这个业务123,这个时候因为业务123的数据被锁了,他要等A线程释放。

🔥巧了两个事务都没有设置超时,那事情就变得奇妙起来了,Spring层面会默认一直等待,直到数据库层面事务超时释放。于是关于这笔业务的一切行为都停止了,全部都在等待事务释放。

🔥🔥更巧的是如果线程B是一个批量更新的操作,那么批量更新的业务可能都被锁起来,这个时候就不仅仅是业务123的问题了,可能整个系统都在面临着极大的考验。

所以面对以下操作,我们必须设置事务超时时间

  • 批量数据处理
  • 第三方API调用
  • 大事务

推荐公式:

工程化一点的版本(推荐用这个) 🚀 $$ T_{timeout} = (T_{sql} + N_{rpc} \times T_{rpc} + T_{logic}) \times (1 + J) $$ 参数说明:

  • T_sql:所有 SQL 总耗时平均值
  • N_rpc:事务里调用外部服务次数
  • T_rpc:一次 RPC / HTTP 平均延迟
  • T_logic:业务代码执行时间
  • J:系统抖动比例(一般 0.3 ~ 1.0

事务隔离

事务隔离是事务的一个特性,我们在开发复杂业务的时候,可能会出现多次更新的情况,如果A线程执行没有结束,可能还会继续更新,但是B线程已经扫描到了这个临时状态,于是就发生了并发问题,对于这种就是未提交读。这个时候是需要添加事务,由事务来保证数据有效。

但是,这种场景更建议再数据确认后统一操作,而不是将保存、更新分散到同一个方法的不同位置。


批量操作优化

批量操作是最容易出现事务的地方,因为批量操作编写不当就会引起系统的阻塞。

坏🌰:

@Transactional
public void batchUpdate(List<Order> list) {
    for (Order order : list) {
        orderMapper.update(order);
    }
}

当这个业务中的某一个Order更新出现竞争阻塞,整个事务都会被阻塞,而参与到更新还没有提交的业务都会一直持有锁,这个时候只能寄希望于设置超时时间了(巧了和第一点对上了 ,大家一般是不设置超时时间的)

处理方案:

降低锁的粒度,尽可能的减少影响

public void batchUpdate(List<Order> list) {

    List<List<Order>> partitions = Lists.partition(list, 200);

    for (List<Order> batch : partitions) {
        orderService.batchUpdate(batch);
    }
}

@Transactional
public void batchUpdate(List<Order> batch) {
    orderMapper.batchUpdate(batch);
}
参考文档:

【1】spring事务中的超时时间很多人都不理解_spring 事务超时-CSDN博客

【2】面试官灵魂拷问:为什么 SQL 语句不要过多的 join? - 知乎