- 业务场景描述
用户表(user) 用户编号 user_id 资金字段 fund
资金明细表(fund_record) 用户编号字段 user_id 更新前资金 before_fund 更新后资金after_fund 消费额度out_fund 增加额度in_fund
需求:在消费用户资金时,记录资金变动记录到明细表
方案:将更新用户表,添加明细表操作,加入同一事务。加事务级别排他锁,事务完毕释放锁。
- sql实现细节简写
用户表:update user set fund=fund-额度 where user_id=用户编号 and fund-额度>=0
明细表:简单插入略(事务过程中有计算流程)。
执行顺序:先update,若成功,查询计算,再添加记录。update不成功,不做后续操作
- 执行结果
100线程并发,单次额度1,用户更新后,延时1秒执行明细添加。最终数据无错误,数据库锁超时。成功执行48条。
- 猜测分析
update 语句添加排他锁,且是事务级别排他锁。所有线程到update处,执行模式转换为串行,所以在当前线程执行完毕前,其他线程被挂起。最终结果,既保证了用户资金更新正确,又保证了明细数据先入先出,资金记录不乱
好吧,添加下反思,上述实验,结果是错误的,对于单条具体记录,单数据库的情况下,update其实是分布式排他锁,但是无法保证资金明细记录不乱,因为时间上是乱序的,之所以正确,是因为只有一台机器访问数据库,如果是多台,明细在多台并发的情况下录入的数据肯定会出现错误数据,原因是读到其他机器更新后的结果,而不是自己更新后的结果。其实用线程模拟多台机器,思路好像没啥问题,但是这种并发的问题,本来就是非必发事件,我猜测是我当时实验数据太小,或者运气太好导致,这并不是真相。对应这种可能出现的并发错误,我现在的解决方案,是在事务入口加redis分布式锁,锁没被释放,那就拉倒,全部失败,这粗暴,但是好用。