mysql并发扣减库存如何实现_mysql原子操作设计

6次阅读

正确做法是 UPDATE 时在 WHERE 中加入库存校验条件,如 WHERE id = 123 AND stock >= 1,并检查 ROW_COUNT();复杂逻辑需配合 SELECT FOR UPDATE 加行锁,且 WHERE 条件避免函数导致索引失效。

mysql 并发扣减库存如何实现_mysql 原子操作设计

UPDATE WHERE 语句必须带库存校验

并发扣减库存最直接的错误,就是只写 UPDATE product SET stock = stock - 1 WHERE id = 123。这会导致超卖:两个请求同时读到 stock=1,各自执行减 1,最终变成 -1。

正确做法是把库存是否充足判断直接塞进 WHERE 条件里,让 MySQL 在更新前原子性校验:

UPDATE product SET stock = stock - 1 WHERE id = 123 AND stock >= 1;

执行后检查 ROW_COUNT()(MySQL 返回影响行数):

  • 返回 1 → 扣减成功
  • 返回 0 → 库存不足或记录不存在,需业务层拒绝下单

务必使用 SELECT FOR UPDATE 配合事务

当扣减逻辑不止一行 SQL(比如要先查价格、再扣库存、再写订单),单纯靠 WHERE 校验不够,必须加行锁防止并发读写冲突。

关键点:

  • 必须在 START TRANSACTION 内执行
  • SELECT …… FOR UPDATE 会锁定该行(即使没命中索引,可能升级为表锁)
  • 锁持续到事务结束(COMMITROLLBACK),不是语句结束

示例:

START TRANSACTION; SELECT stock FROM product WHERE id = 123 FOR UPDATE; -- 此时其他事务对 id=123 的 SELECT FOR UPDATE / UPDATE 会被阻塞 UPDATE product SET stock = stock - 1 WHERE id = 123 AND stock >= 1; -- 检查 ROW_COUNT(),失败则 ROLLBACK COMMIT;

避免 WHERE 条件中使用函数或表达式导致索引失效

如果写成 WHERE id = ? AND stock - 1 >= 0,MySQL 无法用上 stock 索引(哪怕有联合索引),可能触发全表扫描 + 锁表,极大降低并发能力。

应始终保持 WHERE 中的列是独立出现的:

  • WHERE id = 123 AND stock >= 1(能走 PRIMARY KEY + 范围条件)
  • WHERE id = 123 AND stock - 1 >= 0stock - 1 是表达式,索引失效)
  • WHERE id = 123 AND stock > 0(虽然语义等价,但某些旧版本优化器可能不走索引,>= 1 更稳)

高并发场景下慎用自增 ID 做库存分片依据

有人想用 id % 4 把商品分散到不同行来缓解 热点,但这会破坏业务语义(同一商品多行库存难聚合),且无法解决单商品高并发问题。

真正有效的分片思路是:

  • 按业务维度拆分:比如「SKU + 仓库 编码」作为联合主键,不同仓库存独立扣减
  • 用 Redis 预减库存(异步落库),但需处理 Redis 故障 / 回滚一致性问题
  • 数据库端改用乐观锁(version 字段)+ 重试,适合冲突率低的场景

纯 MySQL 方案里,UPDATE …… WHERE …… AND stock >= N + 事务 + 索引覆盖,已经能扛住几千 QPS 的秒杀流量;再往上,就得考虑缓存、队列、分库分表了。

星耀云
版权声明:本站原创文章,由 星耀云 2026-01-04发表,共计1287字。
转载说明:转载本网站任何内容,请按照转载方式正确书写本站原文地址。本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。
text=ZqhQzanResources