SpringData Jpa遇ORA-00060死锁,求代码优化解决方法
解决Spring Data JPA + Envers场景下的ORA-00060死锁问题
你的代码确实存在引发死锁的潜在风险,结合大量并发查询的场景,主要问题和优化方案如下:
问题分析
- 删除顺序不固定:
repository.deleteAll(cars)会循环删除列表中的实体,但如果不同线程查询到的Car列表顺序不一致(比如线程1拿到的是[CarA, CarB],线程2拿到的是[CarB, CarA]),就会出现交叉锁等待:线程1先锁定CarA尝试锁CarB,线程2先锁定CarB尝试锁CarA,最终触发死锁。 - 事务范围过大:当前事务包含了查询、删除、保存全流程,锁的持有时间被拉长,大幅提升了并发场景下的锁冲突概率。
- Envers的额外锁竞争:由于使用Envers,删除实体时会自动插入审计记录,这会额外增加数据库操作,进一步提高锁竞争的可能性。
解决方案
1. 固定实体删除顺序
所有线程以相同顺序删除Car实体是解决交叉死锁的核心手段。可以通过实体的唯一标识(比如id)对列表排序,确保删除顺序一致:
private void deleteOldCars(List<Car> cars) { // 按id升序排序,强制所有线程的删除顺序统一 List<Car> sortedCars = cars.stream() .sorted(Comparator.comparing(Car::getId)) .collect(Collectors.toList()); repository.deleteAll(sortedCars); // 非业务必须的话移除flush操作,避免提前持有锁 // entityManager.flush(); }
2. 缩小锁持有时间
- 尽量缩短事务内的非数据库操作时间,比如
mapAndSaveNewCar中的对象映射逻辑,如果业务允许,可以放在事务外执行。 - 确认
findCarsByBrand查询使用了高效索引,减少查询耗时,从而缩短事务的整体执行时间。
3. 优化事务隔离级别
Oracle默认的隔离级别是READ_COMMITTED,如果你的应用配置了更高的隔离级别(比如REPEATABLE_READ),可以考虑调整为READ_COMMITTED,降低锁竞争的强度。
修改后的完整示例
@Transactional public Result replaceCarWithGivenBrand(Request request) { List<Car> cars = repository.findCarsByBrand(request.getBrand()); deleteOldCars(cars); mapAndSaveNewCar(request); return Result.UPDATED; } private void deleteOldCars(List<Car> cars) { List<Car> sortedCars = cars.stream() .sorted(Comparator.comparing(Car::getId)) .collect(Collectors.toList()); repository.deleteAll(sortedCars); } private void mapAndSaveNewCar(Request request) { // 根据请求数据映射生成新Car列表(补充原代码缺失的逻辑) List<Car> cars = request.getNewCarInfos().stream() .map(info -> { Car car = new Car(); car.setBrand(request.getBrand()); car.setModel(info.getModel()); // 其他属性赋值 return car; }) .collect(Collectors.toList()); repository.saveAll(cars); }
内容的提问来源于stack exchange,提问作者Kamil Makaryk
相关产品推荐
相关产品推荐

