问题背景:事务提交后数据为何不可见?
你是否在分布式系统中遇到过这样的现象?
- 代码正确使用了 分布式锁 和 事务注解,但高并发场景下,事务A提交的数据对事务B仍然不可见。
- 两个请求严格按照锁顺序执行,但第二个请求读取到的仍是旧数据。 这种数据延迟可见问题背后,究竟隐藏着哪些技术细节?让我们从一段典型代码入手分析:
java
@DistributedLock(key = "resource_lock") // 分布式锁切面
@Transactional(rollbackFor = Exception.class) // 事务切面
public void updateOrder() {
// 业务逻辑:更新数据并提交
}
当两个请求并发执行时:
- 请求1 获取锁 → 执行更新 → 提交事务 → 释放锁。
- 请求2 等待锁 → 获取锁后 → 读取数据时未查到最新数据。
这种现象可能导致业务逻辑异常(如超卖、重复操作等)。
核心原理分析
1. 事务隔离级别的影响
MySQL 默认隔离级别为 REPEATABLE READ
,其核心特性包括:
- 快照读(Snapshot Read) :事务内首次查询时创建数据快照,后续读取均基于此快照。
- 不可见性:其他事务提交的数据变更对当前事务不可见(除非事务内重新查询)。
2. 切面执行顺序的关键作用
Spring AOP 中切面的执行顺序由 @Order
值决定:
- 值越小,优先级越高,切面越早执行。
- 事务切面默认优先级最低 (
Order = Integer.MAX_VALUE
)。
典型错误场景
若分布式锁切面优先级低于事务切面,执行流程如下:
- 请求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类
AbstractPointcutAdvisor
中getOrder
方法逻辑可以看到默认是最低优先级。 -
查看事务切面默认配置
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() { ... }
最佳实践建议
- 显式声明切面顺序
对所有自定义切面(如日志、锁、缓存等)显式设置@Order
值,避免依赖默认顺序。 - 隔离级别适配场景
- 高并发读写场景 :优先使用
READ COMMITTED
。 - 数据一致性要求高 :保留
REPEATABLE READ
,但需配合乐观锁机制。
- 高并发读写场景 :优先使用
- 关键操作添加日志
在锁获取/释放、事务开始/提交位置记录日志,便于快速定位问题。
总结
事务间数据不可见问题的本质是 隔离级别特性 与 切面执行顺序 共同作用的结果。通过:
- 确保锁切面优先执行,避免事务过早开启。
- 合理设置隔离级别,平衡一致性与实时性。
- 系统性日志监控,快速验证执行流程。
可有效解决此类问题。理解底层机制并合理设计代码,是构建高可靠分布式系统的关键。