场景:
在某个业务场景需要向关系表x 中增加一条记录,但是x 中有唯一约束.
解决方案:
最直接的解决方案是这样的sql
INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;
但是postgre 9.3 版本中不支持这样的语法
因此变通解决方案
先增加当发发生异常时进行更新
@Service("userAppDefaultOrganizationService")
@Transactional
public class DefaultXxxService implements XxxService {
@Autowired
private DefaultXxxDao userAxDao ;
@Override
public void saveOrUpdate(XxxEntity userAx) {
if (userAx== null) {
return;
}
try {
//新增时独立事务执行当发生异常时执行一次update
userAxDao.saveUserAx(userAx);
} catch (Exception e) {
userAxDao.updateUserAx(userAx);
}
}
//要单独开启事务,否则出现唯一索引异常时 整个事务会回滚,外部在做update 会提示事务已经被回滚不能操作
@Transactional(propagation = Propagation.REQUIRES_NEW , rollbackFor=Exception.class)
public void saveUserAx(UserAxEntity userAx){
userAxMapper.addUserAx(userAx);
}
public void updateUserAx(UserAxEntity userAx){
userAxMapper.updateUserAx(userAx);
}
@Transactional(propagation = Propagation.REQUIRES_NEW , rollbackFor=Exception.class)
public void deleteUserAx(UserAxEntity userAx){
userAxMapper.deleteUserAx(userAx);
}
ok 这样测试可以通过了
但是在实际的应用场景中发现了新的问题,嵌套事务时发生死锁
@Override
public Long findOrAddUserAx(String appKey, Long userId) {
```
//当不在清理掉用无效数据
userAxService.deleteUserAx(uado);
````
//重新计算
````
if(orgId != null){
//本次新计算出了org,此时 保存新的uado到db 中
uado = new UserAxEntity();
uado.setUserId(userId);
uado.setAppKey(appKey);
uado.setOrgId(orgId);
addUserAx(uado);//执行userAxService.add方法 此时程序停住不执行了
}
}
最后分析的原因是 进入findOrAddUserAx 时已经开启事务A
在执行完 userAxService.deleteUserAx(uado); 使用的事务A,并没有提交
下边在执行 addUserAx 时 内部开启了新的事务 执行时 因为表中有一个唯一索引 deleteUserAx 占用资源未提交 ,addUserAx 又要去占用这个锁 因此旧导致了死锁(去掉唯一索引上边代码则正常执行)
解决方案
1, 识别出特殊的场景直接做update,此时可绕过 独立事务的新增
2,deleteUserAx 增加独立事务
//要单独开启事务,否则出现唯一索引异常时 整个事务会回滚,外部在做update 会提示事务已经被回滚不能操作
@Transactional(propagation = Propagation.REQUIRES_NEW , rollbackFor=Exception.class)
public void saveUserAx(UserAxEntity userAx){
userAxMapper.addUserAx(userAx);
}
public void updateUserAx(UserAxEntity userAx){
userAxMapper.updateUserAx(userAx);
}
@Transactional(propagation = Propagation.REQUIRES_NEW , rollbackFor=Exception.class)
public void deleteUserAx(UserAxEntity userAx){
userAxMapper.deleteUserAx(userAx);
}
综上 当使用嵌套事务时 一定要注意 可能引发数据库层面的死锁问题 所以尽量避免嵌套事务.