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

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

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

  • 代码正确使用了 分布式锁事务注解,但高并发场景下,事务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. 系统性日志监控,快速验证执行流程。

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

相关推荐
hykDatabases10 分钟前
Oracle内置函数及自定义函数
数据库·oracle
HelloZheQ11 分钟前
数据库故障排查指南
数据库·oracle
敲上瘾18 分钟前
MySQL数据库与表结构操作指南
linux·数据库·mysql
虾球xz35 分钟前
游戏引擎学习第266天:添加顶部时钟概览视图。
数据库·c++·学习·游戏引擎
可儿·四系桜1 小时前
WebSocket:实时通信的新时代
java·网络·websocket·网络协议
forestsea1 小时前
Maven 插件机制与生命周期管理
java·maven
七月在野,八月在宇,九月在户1 小时前
maven 依赖冲突异常分析
java·maven
金融数据出海1 小时前
黄金、碳排放期货市场API接口文档
java·开发语言·spring boot·后端·金融·区块链
胡斌附体1 小时前
微服务中 本地启动 springboot 无法找到nacos配置 启动报错
java·spring boot·微服务·yml·naocs yml
老友@1 小时前
MySQL 与 Elasticsearch 数据一致性方案
数据库·mysql·elasticsearch·搜索引擎·同步·数据一致性