欢迎使用CSDN-markdown编辑器

来源:互联网 发布:编程的那些事儿(10 编辑:程序博客网 时间:2024/06/05 18:54

论mysql死锁的切入点以及解决方法

>本文主要讲java spring 多线程造成的死锁问题

- 死锁举例*
- 死锁快速解决*
- 死锁本质解决*

@Override@Transactional(rollbackFor=Exception.class)public GeneralReponse saveOrder(OpOrder opOrder, GeneralRequest request) {    Long expireTime = opOrder.getCreationTime().getTime()/1000 + PayConstant.OVER_TIME*60;    //加占用库存    increaseOccupy(opOrder);    //存入op_order表    opOrderMapper.insertSelective(opOrder);    //存入op_order_log表    opOrderLogMapper.insertSelective(new OpOrderLog(opOrder.getUserId(), opOrder.getOrderCode(), opOrder.getActivityId(), opOrder.getSaledId(), expireTime, opOrder.getCreationTime(),opOrder.getNum()));    Map<String,Object> resp = new HashMap<String,Object>(1, 1.0f);    resp.put("orderCode", opOrder.getOrderCode());    return new GeneralReponse(CodeConstant.SUCCESS, null,resp);}//加占用库存操作private void increaseOccupy(OpOrder opOrder) {    OpActivitySetTime opActivitySetTime = opActivitySetTimeMapper.selectByPrimaryKey(opOrder.getTimeId());    if(opActivitySetTime == null) {        throw new ParamErrorException(CodeConstant.PARAM_ERROR, "timeId is error");    }    Long stock = opActivitySetTime.getStock();    Map<String,Object> paramData = new HashMap<String, Object>(4, 1.0f);    paramData.put("activityId", opOrder.getActivityId());    paramData.put("setId", opOrder.getSetId());    paramData.put("timeId", opOrder.getTimeId());    paramData.put("dateTime", opOrder.getDateTime());    OpOrderSaled opOrderSaled = opOrderSaledMapper.queryOrderSaleInfo(paramData);    Map<String,Object> map = new HashMap<String, Object>(2, 1.0f);    if(opOrderSaled == null) {        opOrderSaled = new OpOrderSaled(opOrder.getActivityId(), opOrder.getSetId(), opOrder.getTimeId(), 1L, opOrder.getDateTime());        if(opOrderSaledMapper.insertSelective(opOrderSaled) != 1) {            opOrderSaled = opOrderSaledMapper.queryOrderSaleInfo(paramData);            map.put("id", opOrderSaled.getId());            map.put("stock", stock);            map.put("num", opOrder.getNum());            if(opOrderSaledMapper.increaseOccupyById(map) != 1) {                throw new BusinessErrorException(CodeConstant.BUSINESS_ERROR, "您选择的体验名额已用光,请选择其他的时间或体验");            }        }    } else {        map.put("id", opOrderSaled.getId());        map.put("stock", stock);        map.put("num", opOrder.getNum());        if(opOrderSaledMapper.increaseOccupyById(map) != 1) {            throw new BusinessErrorException(CodeConstant.BUSINESS_ERROR, "您选择的体验名额已用光,请选择其他的时间或体验");        }    }    opOrder.setSaledId(opOrderSaled.getId());}

@Transactional(rollbackFor=Exception.class)public boolean cancelProcedure(String orderCode,Long uid,Integer cancalType,Integer payType,Long saledId,Integer num) {    Map<String,Object> paramData = new HashMap<String, Object>();    paramData.put("orderCode", orderCode);    int flag =  0;    Date now = new Date();    try{        //删除op_order_log表的记录        int deleteNum = opOrderLogMapper.deleteByOrderCode(orderCode);        if(deleteNum == 1) {            paramData.put("updateTime", now);            paramData.put("status", 6);            //更改订单状态            opOrderMapper.updateByOrderCode(paramData);            Map<String,Object> map = new HashMap<String, Object>(2, 1.0f);            map.put("id", saledId);            map.put("amount", num);            opOrderSaledMapper.decreaseOccupyById(map);            flag = 1;        }    } catch (Exception e) {        logger.error("return activity stock occured error", e);        throw new RuntimeException("server error");    }    Map<String,String> cacheKey = new HashMap<String,String>(1,1.0f);    cacheKey.put(String.valueOf(uid)+RedisKeyInfo.USER_ORDER.key(), orderCode);    try {        batchRemoveOrderCache(cacheKey);    } catch(RuntimeException e) {        logger.error("batch remove cache occured error", e);    }    if(flag == 0) {        return false;    }    return true;}

①是下单的操作 ,先在占用库存表做增加操作,然后再订单表以及订单中间表进行插入操作
②是定时取消订单的操作,先删除订单中间表的超时订单,然后更新订单表的状态,回补库存

以上coding有几种导致死锁可能

1:未加索引
因这个项目用的是mysql数据库,innodb引擎。对where判断字段没有加索引,导致在做删除订单中间表操作的时候,使用的是表锁,对整张订单中间表进行了锁定,
②中的 opOrderLogMapper.deleteByOrderCode(orderCode)占据了这把锁,然后进行更新订单表状态的操作,
①中的opOrderMapper.insertSelective(opOrder)做完插入操作占据了订单表的表锁,紧接着做插入订单表的操作
此时①要②中的订单表的表锁,②需要①订单中间表的表锁,造成了死锁

2:两个线程都引入事务 同时对两张表进行增删改操作 如果增删改操作相同的数据行就会出现死锁
如:
①中的对占用库存表进行增加库存的操作opOrderSaledMapper.increaseOccupyById(map),
②中的对占用库存表进行了减的操作opOrderSaledMapper.decreaseOccupyById(map);
如果操作的相同数据行,都要等待双方事务的结束才能进行,也会造成死锁

那如何解决呢

从上面可以看出,死锁的关键在于:两个(或以上)的Session加锁的顺序不一致。
1:暴力解决,直接在mysql杀死进程id
①.查看下在锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
②.杀死进程id(就是上面命令的trx_mysql_thread_id列)
kill 线程ID

2:本质解决
这个问题 很难避免的 有时只能从业务上规避 或者降低事务的隔离级别,我们在coding的过程中,需尽量保证事务不会相互占用,执行顺序尽量保持一致,保证操作的原子性