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

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

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

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

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

相关推荐
LJTYBQ39 分钟前
轻松认识 SQL 关键字,打开数据库操作大门
数据库·笔记·sql
山外有山a42 分钟前
从 Neo4j 数据库中提取数据并绘制图谱
数据库·neo4j
Full Stack Developme3 小时前
SQL 版本历史
数据库·sql
大刀爱敲代码3 小时前
基础算法01——二分查找(Binary Search)
java·算法
追风少年1555 小时前
常见中间件漏洞之一 ----【Tomcat】
java·中间件·tomcat
杰克逊的日记5 小时前
mysql数据实时全量+增量迁移
数据库·mysql·数据迁移
yang_love10115 小时前
Spring Boot 中的 @ConditionalOnBean 注解详解
java·spring boot·后端
郑州吴彦祖7726 小时前
【Java】UDP网络编程:无连接通信到Socket实战
java·网络·udp
linuxxx1106 小时前
centos7 升级MariaDB 到 10.5 或更高版本
数据库·mariadb
换个网名有点难6 小时前
django怎么配置404和500
数据库·django