LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

微信小程序做 PDA 扫码出入库:库存并发控制的 4 个坑

admin
2026年10月31日 18:36 本文热度 180

仓管员拿着 PDA 在仓库里扫码出入库,是很多中小企业的标准操作。但一到大促或盘点高峰,三五个仓管员同时扫同一个 SKU,账面库存经常变成负数、发生超卖,甚至同一笔出库被扣两次。这些问题几乎都出在「并发控制」上。去年双十一,某服装仓就因为「先查后改」的老写法,爆款卫衣被超卖 200 多件,客服电话被打爆,根因正是没处理并发。下面把最常见的 4 个坑和对应的正确做法拆开讲。

​

坑一:先查库存、再改库存,两步不是原子的

最典型的错误写法:先 SELECT stock FROM sku WHERE id=? 拿到库存,在代码里判断够不够,再 UPDATE sku SET stock = stock - ?。两个并发请求同时读到「还剩 10」,都判定通过,都减 5,结果库存变成 5,实际应该剩 0——超卖了 5。

正确的做法是用单条 SQL 把判断和扣减变成原子操作:

-- 扣减库存,只有剩余量足够才生效UPDATE skuSET stock = stock - :deltaWHERE id = :sku_id AND stock >= :delta;
// Node.js(mysql2),靠受影响行数判断成败const [res] =awaitconn.execute(`UPDATE sku SET stock = stock - ? WHERE id = ? AND stock >= ?`,  [delta, skuId, delta]);if (res.affectedRows===0) {// 库存不足或行不存在,直接回滚业务,不要重试扣减thrownewError('库存不足');}

affectedRows === 0 就说明库存不够,没必要在应用层再做判断。这一步把「读—判断—写」压缩进数据库的单行更新,天然防超卖。

坑二:开了事务,却没加锁,更新照样丢

有人会说「我包了事务啊」。但在 MySQL 默认的 REPEATABLE READ 下,两个事务各自读到旧值、各自提交,后提交的会覆盖前一个的修改,发生丢失更新(lost update)。事务隔离并不等于互斥。

需要强一致时,用悲观锁把行锁住:

# Python(SQLAlchemy 风格),事务内加行锁withengine.begin()astx:row=tx.execute(text("SELECT stock FROM sku WHERE id=:id FOR UPDATE"),{"id":sku_id},).fetchone()ifrow.stock<delta:raiseValueError("库存不足")tx.execute(text("UPDATE sku SET stock = stock - :d WHERE id=:id"),{"d":delta,"id":sku_id},)

FOR UPDATE 会让并发的第二个事务阻塞等待,直到第一个提交,从而串行化对同一行的修改。顺便提醒,悲观锁要关注 innodb_lock_wait_timeout(默认 50 秒),高冲突下大量事务排队等待会拖垮连接池,必要时配合超时快速失败,而不是无限等待。如果冲突不频繁、追求高吞吐,可以改用乐观锁:表上加 version 字段,更新时带上版本号,版本不匹配就重试。

UPDATE sku SET stock = stock - :d, version=version+1WHERE id = :id ANDversion= :old_version;

坑三:扫码重复提交,库存被扣两次

PDA 在仓库信号差,扫码后没收到回执就会重发;仓管员也可能手滑连扫两下。结果是同一笔出库扣了两次库存。这不是并发问题,是幂等问题。

最稳的办法是业务唯一键:每一笔出入库明细用「业务单号 + 明细行 ID + 操作类型(in/out)」生成唯一标识,存到一张流水表,并对该标识加唯一索引。重复提交会直接撞唯一约束而失败。

CREATETABLE stock_flow (  id BIGINTPRIMARYKEY AUTO_INCREMENT,  biz_key VARCHAR(64) NOTNULL,  sku_id  BIGINTNOTNULL,  delta   INTNOTNULL,UNIQUEKEY uk_biz (biz_key));-- 重复插入相同 biz_key 会抛 DuplicateKey 异常,应用捕获后视为「已处理」

如果一定要在缓存层先挡一道,可以用 SETNX 给 biz_key 加短期锁,但最终兜底必须落在数据库唯一索引上——缓存随时可能失效,只有数据库约束不会骗你。

坑四:上了多实例,单机锁全部失效

前面如果用的是 synchronized 或进程内锁,一旦服务部署成多副本(K8s、多台机器),进程内锁就管不到别的实例,坑一和坑二的保护瞬间归零。

省心的解法:把库存扣减收敛到数据库单行 UPDATE(坑一那种原子更新)。数据库本身就是分布式环境下唯一的互斥点,不依赖应用实例数量。这一点对微信小程序尤其重要:不少团队把出入库逻辑写在云函数或云开发上,平台会按流量自动扩缩容成多实例,进程内锁从上线第一天起就不可靠,所以更该把扣减收敛到 DB。如果业务复杂、必须先在应用层协调,再上 Redis 分布式锁,但同样要保留数据库唯一约束或原子更新作为兜底,别把正确性押在锁一定不超时上。

四种方案怎么选

方案
适用场景
优点
代价
单行原子 UPDATE
简单扣减、防超卖
无需加锁、天然分布式安全
不能做复杂前置校验
悲观锁 FOR UPDATE
强一致、冲突多
逻辑直观、不丢更新
高并发下锁等待,吞吐下降
乐观锁 version
冲突少、追求吞吐
无锁等待
冲突时要重试,写失败率上升
Redis 分布式锁
需应用层协调
灵活
要处理超时/续期,须 DB 兜底

上线前自查清单

  • [ ] 扣减用单条 UPDATE ... WHERE stock >= ?,靠 affectedRows 判成败

  • [ ] 需要复杂校验时加 FOR UPDATE 或用 version 乐观锁

  • [ ] 每笔流水有业务唯一键 + 数据库唯一索引,保证幂等

  • [ ] 多实例部署下不依赖进程内锁,扣减收敛到 DB

  • [ ] 缓存去重只做第一道,最终正确性由数据库约束保证

并发控制没有银弹,但把这 4 个坑堵上,PDA 扫码出入库的库存就能稳得住。


阅读原文:点击这里


该文章在 2026/10/31 18:36:07 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-9  粤公网安备44030602007207号