今天爱分享给大家带来乐观锁、悲观锁、数据库行锁 RPC调用的幂【面试题详解】,希望能够帮助到大家。
先说结论:建议优先用数据库行锁的做法更新额度。mysql数据库的tps我了解到的一个值是4000,大部分场景是满足的。各应用的真实情况还需要自行再压测。不建议刚开始就引入redis等中间件增加复杂度。
以下主要讨论:乐观锁、悲观锁、数据库行锁。RPC调用的幂等问题
数据库行锁(建议):通过where 条件限制不会超额或者超卖
// 其中seq=#{seq}的seq指额表记录的业务主键:在根据个人限额的场景下可能是身份证信息或者卡号;在根据产品限额的场景下可能就是产品码 String sql="update limit_info set avai_amount= avai_amount - #{current} where seq=#{seq} and avai_amount > #{current}"; int res = excute(sql); //根据影响的行数,res是0还是1判断更新是否成功
以下列举下悲观锁及乐观锁的写法以及可能存在的问题
悲观锁:select…for update。缺点:性能会有所下降,大部分业务场景不需要悲观锁。
//开启事务 begin(); Bigdecimal avaiAmount = excute("select avai_amount from limit where id=#{id} for update"); Bigdecimal newAvaiAmount = avaiAmount - current; //判断余额是否足够 //更新余额 if(newAvaiAmount.compareTo(BigDecimal.ZERO) >= 0){ int res = excute("update limit_info set avai_amount=#{newAvaiAmount} where id=#{id}"); } commit();
乐观锁:update时添加条件avai_amount=#{avaiAmount}。缺点:要有重试机制,增加了代码复杂度。
Bigdecimal avaiAmount = excute("select avai_amount from limit_info where id=#{id}"); Bigdecimal newAvaiAmount = avaiAmount - current; //判断余额是否足够 //更新余额 if(newAvaiAmount.compareTo(BigDecimal.ZERO) >= 0){ int res = excute("update limit_info set avai_amount=#{newAvaiAmount} where id=#{id} and avai_amount=#{avaiAmount}"); }
ps: 互金理财应用里关于按天控制额度用sql的写法(基金生产在用的做了简化):
--冻结客户交易份额 update LIMIT_INFO set USED_AMT = CASE WHEN WORK_DATE = #workDate# then USED_AMT + #usedAmt# else #usedAmt# end, WORK_DATE = #wokdDate#, MAX_AMT = #maxAmt#, where CST_NO = #cstNo# AND CASE WHEN WORK_DATE = #workDate# then USED_AMT + #usedAmt# else #usedAmt# end <= MAX_AMT --回滚已使用额度 update LIMIT_INFO SET USED_AMT = CASE WHEN WORK_DATE = #workDate# then USED_AMT - #usedAmt# else 0 end, WORK_DATE = #workDate#, WHERE CST_NO = #cstNo# AND WORK_DATE <= #workDate# AND (CASE WHEN WORK_DATE = #workDate# then USED_AMT - #usedAmt# else 0 end) >= 0