今天在同事碰到了一个比较有意思的问题,为了实现某个场景中的数据更新和删除,想通过 delete all entities 然后 insert new entities 的方式减少判断数据是否删除的操作,结果由于表内有其他唯一索引报错唯一键冲突。
后面在 debug 的过程中,用 evaluate 查询数据确实不在,但是 insert 的时候会报唯一键冲突导致插入失败事务回滚。
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [unique_format_content_language] ...
- 下面大概复现下问题
mysql 版本 5.x.x 事务隔离级别 read commit
public class User {
private Long id; // primary key
private Long uk; // foreign key
private String className; // key
private String name;
private int age;
}
大概通过下面的方式进行先删除,再插入。这里通过班级名称来统一删除相关用户,再插入传进来的最新版本。
@Transactional
public boolean update(List<User> users, String className) {
userRepository.deleteByClassName(className);
userRepository.insertBatch(users);
return true;
}
这里由于通过 className 这个普通索引进行删除,在事务中仅对这些数据做了标记,并没有真正从磁盘中删除。并且也没有对唯一索引进行更新,从而导致后面插入新版本数据的过程中,如果是修改的数据那么会在唯一索引找到重复的键从而导致冲突。
这里的解决方法是可以通过 pk 或者 uk 进行删除,这样会去更新索引,从而避免冲突。
todo:事务中索引的变更
当然这里也可以将班级的其他人查出来,然后通过比较找到 insertList、updateList 以及 deleteList。