需求背景:实现一个钱包系统的充值和提现功能,需要确保同一商户下交易单号唯一,以防止同一时间重复充值问题。
前提说明:使用python加上mongotea,通过orm的方式操作数据库
model介绍
from mongotea.models.base import BaseModel
class SettlementTrade(BaseModel, StateFieldOperation):
"""结算单
"""
__collection__ = 'trade'
logger = logging.getLogger(__name__)
indexes = [
{
'fields': [('trade_no', -1), ('merchant_id', -1)],
'unique': True,
},
]
structure = {
# ID
'_id': ObjectId,
# 商户ID
'merchant_id': ObjectId,
# 用户ID
'account_id': ObjectId,
# 交易金额(分)
'money': int,
# 状态(1:待交易, 50:交易中, 100:交易完成, -100:交易失败, -101:交易关闭)
'state': int,
# 单据类型(10:充值单, 30:提现单)
'trade_type': int,
# 交易单号
'trade_no': str,
}
开始实现方案:biz编写查询条件来判断交易单号是否重复,代码如下
spec = {
'trade_no': trade_no,
'merchant_id': merchant_id,
}
record = db.SettlementTrade.find_one(spec)
if record:
logger.info(u'>>>>> Trade no<{}> already exist.'.format(trade_no))
raise BizRuntimeError(zh_message=u'交易单号重复,请稍后重试!')
但是在高并发的情况中是拦不住订单重复的问题,在数据表中还没有某个交易单号数据时,两个交易单号相同的写入请求同时进来了,他们同时做了判断,都得到当前字段不存在的结果,此时两个连接对象都将会做插入的操作,这样导致数据表中同时存在两个交易单号相同的订单。针对以上场景我们可以巧妙借用mongodb的唯一索引来解决并发问题,具体修改方案如下:
1、首先给collection创建组合唯一索引
db.trade.ensureIndex({"trade_no":-1, "merchant_id": -1},{"unique":true})
2、代码捕获DuplicateKeyError异常反馈给api调用者
try:
doc = {
'merchant_id': merchant_id,
'money': money,
'trade_no': trade_no,
'trade_type': RECHARGE
}
record = db.Trade()
record.update(doc)
record.save()
except DuplicateKeyError as e:
logger.exception(e)
raise BizRuntimeError(zh_message=u'交易单号重复,请稍后重试!')
return record