错误代码
@Component
@Slf4j
public class PropertyNameStatManagerImpl implements PropertyNameStatManager {
// 批量保存
@Override
@Transactional(rollbackFor = Exception.class)
public void batchSavePropertyStat(List<PropertyStatDto> dtos) {
……
// 批量保存属性值和属性名称
for (PropertyStatDto dto : dtoList) {
// 位置1
this.save(dto);
}
}
// 批量删除
@Override
@Transactional(rollbackFor = Exception.class)
public void batchRemovePropertyStat(List<PropertyStatDto> dtos) {
……
// 批量删除属性值和属性名称
for (PropertyStatDto dto : dtoList) {
// 位置2
this.remove(dto);
propertyValueStatManager.remove(dto);
}
}
// 单个保存
@Override
public synchronized void save(PropertyStatDto dto) {
saveOrRemove(dto, 1);
}
// 单个删除
@Override
public synchronized void remove(PropertyStatDto dto) {
saveOrRemove(dto, -1);
}
/**
* 保存 / 删除 统计信息
*
* @param dto 统计信息
* @param changeNum 改变数量
* @author 明快de玄米61
* @date 2022/11/21 12:06
**/
private void saveOrRemove(PropertyStatDto dto, Integer changeNum) {
// 获取最新统计结果
PropertyNameStat stat = null;
List<PropertyNameStat> stats = service.list(Wrappers.<PropertyNameStat>lambdaQuery().eq(PropertyNameStat::getPropertyId, dto.getPropertyId()));
if (stats.isEmpty()) {
PropertyNameStat entity = insertPropertyNameStat(dto.getPropertyId(), dto.getPropertyName());
if (entity != null) {
stat = entity;
}
} else {
stat = stats.get(0);
}
// 判断统计结果
if (stat == null) {
String ops = changeNum > 0 ? "添加" : "删除";
log.error("》》》%s属性名称统计结果失败,参数:%s", ops, JSON.toJSONString(dto));
return;
}
// 位置3
// 更新统计结果
UpdateWrapper<PropertyNameStat> wrapper = new UpdateWrapper<>();
wrapper.eq(PropertyNameStat.PROPERTY_ID, dto.getPropertyId());
String operator = changeNum > 0 ? "+" : "";
wrapper.setSql(PropertyNameStat.NUM + "=" + PropertyNameStat.NUM + operator + changeNum);
service.update(null, wrapper);
}
/**
* 保存属性名称统计对象
*
* @param propertyId 属性id
* @param propertyName 属性名称
* @return 属性名称统计对象
* @author 明快de玄米61
* @date 2022/11/21 14:05
**/
private PropertyNameStat insertPropertyNameStat(String propertyId, String propertyName) {
// 初始化
PropertyNameStat entity = new PropertyNameStat();
entity.setPropertyId(propertyId);
entity.setPropertyName(propertyName);
entity.setDataEnable(EnableStatusEnum.ENABLE.getCode());
entity.setDataDelete(DeleteStatusEnum.NOT_DELETE.getCode());
entity.setNum(0L);
// 添加
boolean save = service.save(entity);
return save ? entity : null;
}
}
分析
如果用户A执行batchSavePropertyStat()
方法,然后执行了位置1的this.save()
方法,由于save()
方法里面有saveOrRemove()
方法,该方法中我用了update
语句,由于我暂时没有为update
语句中where
后面的字段添加索引,这样就会触发锁表,详情请看:数据库update语句到底是行锁还是表锁?,由于事务还没有结束,然后this.save()
结束,准备在位置1在执行this.save()
此时用户B执行batchSavePropertyStat()
方法,然后执行了位置1的this.save()
方法,由于save()
方法有synchronized锁,那么用户A就要等待,此时用户B去执行this.save()
方法,,由于save()
方法里面有saveOrRemove()
方法,该方法中我用了update
语句,由于我暂时没有为update
语句中where
后面的字段添加索引,所以update方法执行会先获取表锁,但是此时用户A控制的事务没有提交,这样就形成了表死锁