引言
在现代软件开发中,尤其是在涉及高并发场景的应用,如在线购物、金融交易等,保证数据操作的原子性和一致性是至关重要的。本文将深入探讨并发环境下常见的问题,以及如何通过先进的技术手段确保数据的准确性和安全性。
案例分析:库存扣减的并发挑战
背景设定
设想一个在线电商平台,其后端数据库中有一个名为 stock
的库存数据表,其中 stock_num
字段记录了商品的库存数量。
并发扣减库存的问题
在高流量的购物节期间,库存扣减操作变得尤为关键。一个典型的扣减操作可能是执行如下 SQL 语句:
UPDATE stock SET stock_num = stock_num - 1 WHERE product_id = X;
以上操作会产生什么问题?
**不幂等:**当上次语句重复执行的时候会导致多次扣减库存,为什么会重复执行,比如业务侧重试、数据库代理重试等,因此我们可以先查出库存数量,得出新库存等于 new_stock = stock_num - 1, 最终语句如下:
update stock set stock_num = new_stock
**超卖:**并发场景解决了不幂等问题之后依然存在超卖问题,假如库存剩余1,两个并发请求同时查询库存为1,此时 new_stock_num = 1-1 ,两个请求同时执行 update stock set stock_num = new_stock_num,只有一个库存却卖出两件
怎么解决超卖问题
CAS(Compare-And-Swap) 机制的应用:我们在语句后面加上库存数量判断即可 ,然后根据 affetc row 0或1判断执行有没有成功,语句如下:
update stock set stock_num = new_stock_num where stock_num = old_stock_num
解决以上问题就万事大吉了吗,其实以上写法还有可能存在经典 ABA 问题
深入探讨:ABA 问题的识别与解决
尽管 CAS 机制可以解决不幂等性和超卖问题,但它本身也有局限性,即所谓的 ABA 问题。ABA 问题描述了在并发编程中,一个值从 A 变为 B,再变回 A,而 CAS 操作却可能错误地认为值未发生变化。
举例解释:
-
张三去银行取钱,余额有 200 元,张三取 100 元,但因为程序的问题,启动了两个线程,线程一和线程二进行比对扣款,线程一获取原本有 200 元,扣除 100 元,余额等于 100 元 - 此时余额是100
-
李四给张三转账 100 元,于是启动了线程三抢先在线程二之前执行了转账操作,把 100 元又变成了 200 元 - 经过转账又变成了200
-
此时线程二对比自己事先拿到的 200 元和此时经过改动的 200 元值一样,就进行了减法操作,把余额又变成了 100 元 - 线程2是步骤一异常启动的线程,我们期望线程2不应该扣款但由于执行语句的判断条件是 old_money=200, 由于 ABA 问题导致条件判断成功,导致多扣了100
解决方案:引入版本号
ABA 问题的核心原因是因为被更新的变量会有加减操作,所以导致值会在新老数值之间变动,如果值只加不减,那么判断条件是不是就可以成立了,所以我们可以引入一个版本号字段 version,此时更新语句变成:
update stock set stock_num = new_stock_num, version = new_version where stock_num = old_stock_num and version = old_version
Reference
- 深入理解 CAS 和 ABA 问题