最近接了一个需求,是要对一些交易接口做防止交易重复执行的改造。

由于该项目比较老了,没有使用spring框架、redis等技术,所以在防重复交易的方案设计,当时组内讨论的方向是使用数据库的锁机制。

背景:这是一个bank系统,外围系统A本身可以做额度扣减/恢复操作,然后会通知 本系统进行同步扣减/恢复处理。但是外围系统A,在调用本系统接口时,本身没有处理是否重复发送接口,所以导致本系统有可能重复扣减/恢复额度。

在改造前,翻看已有代码,发现以前的人,也做了一些防重处理:处理前先查询是否有 处理记录,如果没有,则进行交易操作,交易结束,会记录本次交易记录,用于下次查询是否已处理。但是,明显这个机制在并发发生的时候,是完全不适用的。当真正发生并发时,2个并发请求的select 操作返回的,肯定都是null,然后2个并发请求都会进行交易,导致最终还是重复交易。

整改方案

大体设计逻辑:利用数据库本身的锁机制,保证交易留痕的唯一性。

1.和外围系统沟通,设计一个交易流水号:每一笔交易,流水不一样;同笔交易,流水必须一样。

2.在1成立的前提下,每次交易前,先往流水表insert一个记录(记录状态为正在处理中),该记录以流水号为主键。若insert成功,则继续后面交易逻辑,否则若数据库返回主键冲突,则提示外围系统该流水已存在,可通过回查接口确认交易情况。

3.在2步骤insert成功后,开启事务,并对流水号为S(当前交易流水)的记录进行一次update操作。这个update操作旨在利用数据库的行锁机制(所以update的内容是无关重要的),保证该update操作后的处理是同步操作(synchronized),不存在并发问题:因为如果多个事务对同一行记录进行update操作时,数据库本身会有行锁机制,保证update 的正确性。同一时间只能一个事务更新一个记录,其他事务需等待前面的事务提交后执行。

4.处理完成,更新流水状态为成功或失败。并提交事务。

5.提供一个回查接口,供外围确认交易结果。

综上,该方案,基于插入主键时的互斥机制,以及数据库更新记录时的行锁机制。当然,保证交易流水号的唯一性是前提。