日期:2014-05-16 浏览次数:20680 次
最近遇到一个比较棘手的问题,交易时出现重复交易,并且这个问题是偶尔才出现,公司的产品主要是针对餐饮行业的CRM管理系统,类似于开卡,做消费奖励活动等 ,一天的交易量大,商户有几百家,门店数千个,至于为什么为出现重复交易,虽然在程序里面已经控制了是否重复提交的限制(也就是根据transId去查是否已经存在),但是仍然会出现重复交易的现象。在追究为什么在有重复提交限制还出现这种问题上,答案很模糊,连技术总监也直言,重复交易的原因很不确定,可能由于网络原因造成多次发出请求,操作失误等(比如多次点击鼠标)等 。
???? 程序中判断是否是重复提交的代码:
public boolean checkRepeatTrans(String bizId, String posId) { Map<String, Object> parameter = new HashMap<String, Object>(); parameter.put("bizId", bizId); parameter.put("posId", posId); TransRecord transRecord = (TransRecord) getSqlMapClientTemplate().queryForObject("TransRecord_SqlMap.getInstanceByBizId", parameter); if (transRecord != null) throw new AppException(PosErrors.REPEAT_TRADE); return true; }
?if (transRecord != null) throw new AppException(PosErrors.REPEAT_TRADE);
这一句,如果相同的bizId和posId,则表示此交易已经存在,就会抛出重复交易的异常。看似这样做已经没有问题,但是还是出现了重复交易的问题,bizId和posId完全一样。可以判断是由于并发造成的重复提交,之前处理防重复交易,大概也就和这个层次一样,没有再深入到其他层次。所以问了项目经理解决策略。还是PM有经验,一看bizId,PosId一样,然后说了一句,“是重复交易,加个行锁就能解决了”,之前有了解过Hibernate的悲观锁,乐观锁,对于锁机制一知半解,之前所做的都是web网站,流量不高,所以都没有考虑并发问题。这次算是理解锁机制,通过搜集一些有关锁的机制,今天就来总结一下我自己的理解,分享与交流,经验有限,总结的或许有不足或者错误之处,多提改进修正建议,在此感谢。
????? 先来一段有关锁,事务的总结的概括吧:
?
select * from MEMBER_CREDIT_ACCOUNT where merchant_id = '01058121106' and customer_id='0010511200000971' for update?,执行后明显看到,PLSql 左上角有提交或者回滚的键变成可点状态了。这个时候不做任何操作,不提交也不回滚,然后再打开另一个PLSQL窗口,执行一个读的操作,也就是select 语句,这个语句能够马上查出来。也就是证明,在加了修改锁的时候,读是不会阻塞的。然后再写一个update语句测试写的操作,?执行的时候发现右下角一直出现
update MEMBER_CREDIT_ACCOUNT set store_id = '332' where merchant_id = '01058121106' and customer_id='0010511200000971'?
????解决这次的问题,我采用的是行级锁。是用select? for update 去给某一行加锁,并且,考虑给哪个表加锁,还要考虑具体的业务,因为加了行锁的话,也就是加了一个事务,在这个事务没有提交或者回滚之前,其他的事务都得排队等待,在没有提交事务或者回滚前,假如这一条数据影响的其他操作,比如,锁定了会员预存表中的某一条数据,
select * from MEMBER_ACCOUNT where merchant_id = '01058121106' and customer_id='0010511200000971' for update
?那么假如这个时候营业员从管理台手工调账,调整这个customer预存,那么这个操作就会等很久不会执行(一个极端的模拟方式,锁住这一条数据,项目在调试状态,断点还没有执行到事务提交或者回滚时,后台对这个用户手工调账的操作就会反应很慢,是因为还在等待这个锁定的事务提交)。因此,在考虑锁哪个表的某一行时,一定要找到对整个应用系统中影响最小的那个表。
?
?
????? 首先结合我的程序代码来看:
?
?
?
public Map<String, Object> creditConsume(Map<String, String> parameter) { String posId = parameter.get(ApiConstants.PARAM_POS_ID); String posPwd = parameter.get(ApiConstants.PARAM_POS_PWD); String storeId = parameter.get(ApiConstants.PARAM_STORE_ID); String cardNum = parameter.get(ApiConstants.PARAM_CARD_ID); String transMoney = parameter.get(ApiConstants.PARAM_TRANS_MONEY); String bizId = parameter.get(ApiConstants.PARAM_BIZ_ID); String batchId = parameter.get(ApiConstants.PARAM_BATCH_ID); //判断是否为重复交易 apiAuthenticate.checkRepeatTrans(bizId, posId);
?
其