异步批量删除用户活动实体触发EmptyResultDataAccessException问题排查
问题原因及解决方案
出现这种尝试删除不存在实体的情况,核心原因是查询ID和删除操作不是原子性的,中间存在时间窗口导致数据发生变化,具体可能的场景:
- 并发任务冲突:如果这个异步调度任务被同时触发多个实例,或者有其他业务逻辑也在删除UserActivity实体,就会出现:A实例查询到ID461,在A执行删除前,B实例已经把这个ID的实体删掉了,等A去删的时候自然找不到。
- 事务间隙:查询操作和删除操作分属两次数据库交互,哪怕是同一个事务,也可能因为其他已提交的事务在这两个操作之间删除了目标实体。
针对你的代码,有几种优化方式可以避免这个问题:
直接批量删除,跳过查询ID的步骤
不用先查ID再循环删除,直接在Repository里定义批量删除方法,比如:@Modifying @Transactional void deleteByCreatedBefore(LocalDateTime createdBefore);这样数据库层面直接执行删除逻辑,是原子操作,不会出现查删不一致的情况。
删除前先检查实体是否存在
如果必须保留循环删除的逻辑,可以在deleteById前先判断实体是否存在:if(userActivityRepository.existsById(uaId)) { userActivityRepository.deleteById(uaId); }不过要注意,exists和delete依然不是原子的,极端并发下还是可能出现问题,但能减少报错概率。
捕获特定异常并忽略
既然你已经捕获了Exception,也可以针对性捕获EmptyResultDataAccessException,然后只打日志不做额外处理,因为这种情况本质是数据已经被删了,不需要再执行操作。
内容的提问来源于stack exchange,提问作者Valery
相关产品推荐
相关产品推荐

