你是否也写过这样的代码?为什么事务提交后数据依然“不可见”?

问题背景:事务提交后数据为何不可见?

你是否在分布式系统中遇到过这样的现象?

  • 代码正确使用了 分布式锁事务注解,但高并发场景下,事务A提交的数据对事务B仍然不可见。
  • 两个请求严格按照锁顺序执行,但第二个请求读取到的仍是旧数据。 这种数据延迟可见问题背后,究竟隐藏着哪些技术细节?让我们从一段典型代码入手分析:
java 复制代码
@DistributedLock(key = "resource_lock") // 分布式锁切面
@Transactional(rollbackFor = Exception.class) // 事务切面
public void updateOrder() {
    // 业务逻辑:更新数据并提交
}

当两个请求并发执行时:

  1. 请求1 获取锁 → 执行更新 → 提交事务 → 释放锁。
  2. 请求2 等待锁 → 获取锁后 → 读取数据时未查到最新数据

这种现象可能导致业务逻辑异常(如超卖、重复操作等)。

核心原理分析

1. 事务隔离级别的影响

MySQL 默认隔离级别为 REPEATABLE READ,其核心特性包括:

  • 快照读(Snapshot Read) :事务内首次查询时创建数据快照,后续读取均基于此快照。
  • 不可见性:其他事务提交的数据变更对当前事务不可见(除非事务内重新查询)。

2. 切面执行顺序的关键作用

Spring AOP 中切面的执行顺序由 @Order决定:

  • 值越小,优先级越高,切面越早执行。
  • 事务切面默认优先级最低Order = Integer.MAX_VALUE)。

典型错误场景

若分布式锁切面优先级低于事务切面,执行流程如下:

  1. 请求2等待锁时,事务已开启(事务切面先执行)。
  2. 请求1提交事务后,请求2的事务仍基于旧快照读取数据。

验证步骤

1. 查看数据库隔离级别

sql 复制代码
-- 查看当前数据库隔离级别
SELECT @@global.transaction_isolation, @@session.transaction_iscision;

若结果为 REPEATABLE-READ,需考虑快照读的影响。

2. 检查切面执行顺序

  • 确认分布式锁切面优先级
    确保其 @Order 值小于事务切面的默认值(Integer.MAX_VALUE):

    java 复制代码
    @Aspect
    @Component
    public class DistributedLockAspect { ... }

    这里我们的分布式锁切面并没有显示指定优先级,那么从spring的aop类AbstractPointcutAdvisorgetOrder方法逻辑可以看到默认是最低优先级。

  • 查看事务切面默认配置
    Spring 事务切面默认优先级为最低,无需额外配置,但需确保未被其他配置覆盖。

当分布式锁的优先级和事务优先级都是最低时就是看运气的时候了,运气好分布式锁执行顺序排在事务注解前面可能就没有问题,否则业务就可能会出现bug

解决方案

方案一:调整事务切面执行顺序

确保分布式锁切面先于事务切面执行

java 复制代码
@Configuration
public class AopOrderConfig {
    @Bean
    public BeanFactoryPostProcessor adjustAopOrder() {
        return beanFactory -> {
            // 提升分布式锁切面优先级
            BeanDefinition txAdvisor = ((ConfigurableListableBeanFactory) beanFactory)
                .getBeanDefinition("org.springframework.transaction.config.internalTransactionAdvisor");
            txAdvisor.getPropertyValues().add("order", Ordered.LOWEST_PRECEDENCE + 1);
        };
    }
}

方案二:调整分布式锁执行顺序

java 复制代码
   @Aspect
   @Component
   @Order(Ordered.HIGHEST_PRECEDENCE) // 显式指定高优先级
   public class DistributedLockAspect { ... }

方案三:调整事务隔离级别

将事务隔离级别设为 READ COMMITTED,使事务能读取最新提交数据:

java 复制代码
@Transactional(
    isolation = Isolation.READ_COMMITTED, // 关键配置
    rollbackFor = Exception.class
)
@DistributedLock(key = "resource_lock")
public void updateResource() { ... }

最佳实践建议

  1. 显式声明切面顺序
    对所有自定义切面(如日志、锁、缓存等)显式设置 @Order 值,避免依赖默认顺序。
  2. 隔离级别适配场景
    • 高并发读写场景 :优先使用 READ COMMITTED
    • 数据一致性要求高 :保留 REPEATABLE READ,但需配合乐观锁机制。
  3. 关键操作添加日志
    在锁获取/释放、事务开始/提交位置记录日志,便于快速定位问题。

总结

事务间数据不可见问题的本质是 隔离级别特性切面执行顺序 共同作用的结果。通过:

  1. 确保锁切面优先执行,避免事务过早开启。
  2. 合理设置隔离级别,平衡一致性与实时性。
  3. 系统性日志监控,快速验证执行流程。

可有效解决此类问题。理解底层机制并合理设计代码,是构建高可靠分布式系统的关键。

相关推荐
花菜会噎住5 分钟前
数据库入门:从零开始构建你的第一个数据库
数据库·sql·oracle
山茶花开时。11 分钟前
[Oracle] DECODE()函数
数据库·sql·oracle
尚学教辅学习资料27 分钟前
SpringBoot3.x入门到精通系列:4.1 整合 MongoDB 详解
数据库·mongodb·springboot3
Absinthe_苦艾酒30 分钟前
MongoDB学习专题(五)索引
数据库·后端·mongodb
用户849137175471644 分钟前
JDK 17 实战系列(第3期):性能优化与系统增强详解
java·后端·性能优化
下页、再停留1 小时前
【PHP】对数据库操作:获取数据表,导出数据结构,根据条件生成SQL语句,根据条件导出SQL文件
数据库·sql·php
Asu52021 小时前
思途spring学习0807
java·开发语言·spring boot·学习
遇见火星1 小时前
Jenkins全链路教程——Jenkins用户权限矩阵配置
java·矩阵·jenkins
埃泽漫笔2 小时前
什么是SpringBoot
java·spring boot
zhang1062092 小时前
PDF注释的加载和保存的实现
java·开发语言·pdf·pdfbox·批注