Using @Transactional, synchronized, and Mysql update statements together may cause table deadlock

Table of contents

error code

@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;
    }
}

analyze

If user A executes batchSavePropertyStat()the method, and then executes this.save()the method at position 1, since save()there are saveOrRemove()methods in the method, I used updatea statement in this method, and because I have not added an index for the fields behind updatethe statement , this will trigger the lock table. For details, please whereLook: Is the database update statement a row lock or a table lock? , since the transaction is not over yet, then this.save()end, ready to be executed at position 1this.save()

At this time, user B executes batchSavePropertyStat()the method, and then executes this.save()the method at position 1. Since save()the method has a synchronized lock, user A has to wait. At this time, user B executes this.save()the method. Since save()there are saveOrRemove()methods in the method, I used updatethe statement in this method , since I have not added indexes for the fields behind updatethe statement where, the execution of the update method will acquire the table lock first, but at this time the transaction controlled by user A has not been committed, thus forming a table deadlock

Guess you like

Origin blog.csdn.net/qq_42449963/article/details/130283187