mysql事务并发下如何扣减库存_mysql高并发实现示例
发布时间 - 2026-01-31 00:00:00 点击率:次并发下直接UPDATE库存会超卖,因“读-改-写”非原子:多事务读同值(如10)、各减1后写回,最终仅扣1次变9;本质是未锁住读写间隙,需用UPDATE...WHERE stock>=1原子扣减或乐观锁version机制解决。
为什么直接 UPDATE 库存会超卖
在并发请求下,多个事务同时读取同一行库存值(比如 10),各自减 1 后写回,最终可能只扣了 1 次,变成 9 而不是预期的 8。本质是「读-改-写」非原子操作,中间被其他事务插队。
典型错误写法:SELECT stock FROM goods WHERE id = 1 → 应用层计算 new_stock = old_stock - 1 → UPDATE goods SET stock = new_stock WHERE id = 1。这组操作没有锁住读取到写入之间的窗口。
- 即使加了
SELECT ... FOR UPDATE,若没在事务内、或没正确开启事务(如 autocommit=1),锁会立刻释放 - WHERE 条件未命中索引时,
FOR UPDATE可能升级为表锁,拖垮性能 - 应用层做判断(如
if (stock > 0))再更新,判断和更新之间仍有竞态
用 UPDATE ... WHERE 原子扣减 + 影响行数校验
把「判断是否足够」和「扣减」合并进一条 SQL,由 MySQL 保证原子性。执行后检查 affected_rows 是否为 1,为 0 表示库存不足或记录不存在。
示例(InnoDB,goods 表主键为 id,stock 为 INT):
BEGIN; UPDATE goods SET stock = stock - 1 WHERE id = 1 AND stock >= 1; -- 应用层检查上一条 UPDATE 的影响行数 -- 若为 0:抛异常或返回“库存不足” COMMIT;
-
AND stock >= 1是关键,它让 UPDATE 自带业务条件,失败不修改数据 - 必须搭配事务,否则无法回滚;且建议显式
BEGIN/
COMMIT,避免依赖 autocommit 配置 - 该语句会为满足条件的行加行级 X 锁,其他并发 UPDATE 会被阻塞直到前一个事务结束
乐观锁方案:version 字段 + CAS 更新
适合读多写少、冲突概率低的场景。不依赖数据库锁,靠应用层重试降低阻塞。
goods 表增加 version INT 字段(初始为 0),每次更新都校验并递增:
UPDATE goods SET stock = stock - 1, version = version + 1 WHERE id = 1 AND stock >= 1 AND version = 5;
- 应用先
SELECT id, stock, version FROM goods WHERE id = 1,拿到当前 version(比如 5) - 拼装 UPDATE 语句时带上
version = 5,确保没被别人改过 - 若
affected_rows == 0,说明 version 已变,需重新 SELECT → 计算 → 重试(建议限制重试次数,如 3 次) - 注意:version 冲突时不能简单 sleep 后重试,要重新查最新 stock,否则可能用过期值继续扣
高并发下的实际取舍点
真实系统里没有银弹。悲观锁(FOR UPDATE 或 WHERE 扣减)吞吐受限于数据库行锁排队;乐观锁在秒杀场景下重试风暴可能打崩应用。
- 秒杀类极端场景:建议前置 Redis 计数器预减(Lua 原子),成功后再走 MySQL 扣减,失败则快速返回
- 普通电商下单:优先用
UPDATE ... WHERE stock >= N,简单可靠,配合合理索引和连接池即可扛住数千 QPS - 务必给
goods.id加主键索引,WHERE 中的stock条件若频繁使用,可考虑联合索引(id, stock)加速范围判断 - 别忽略事务隔离级别:
READ COMMITTED足够,REPEATABLE READ下间隙锁可能引发死锁,尤其 WHERE 条件未命中时
# mysql
# redis
# go
# 并发请求
# 为什么
# red
# 有锁
# lua
# sql
# if
# for
# select
# int
# 并发
# 数据库
# 重试
# 应用层
# 死锁
# 锁住
# 主键
# 行数
# 多个
# 不存在
# 秒杀
# 仍有
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel怎么做数据加密_Laravel内置Crypt门面的加密与解密功能
Laravel如何获取当前登录用户信息_Laravel Auth门面使用与Session用户读取【技巧】
瓜子二手车官方网站在线入口 瓜子二手车网页版官网通道入口
谷歌Google入口永久地址_Google搜索引擎官网首页永久入口
Laravel Eloquent性能优化技巧_Laravel N+1查询问题解决
QQ浏览器网页版登录入口 个人中心在线进入
如何在IIS服务器上快速部署高效网站?
Gemini手机端怎么发图片_Gemini手机端发图方法【步骤】
如何在服务器上三步完成建站并提升流量?
敲碗10年!Mac系列传将迎来「触控与联网」双革新
详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)
Python3.6正式版新特性预览
Laravel如何配置.env文件管理环境变量_Laravel环境变量使用与安全管理
Laravel怎么创建控制器Controller_Laravel路由绑定与控制器逻辑编写【指南】
标题:Vue + Vuex + JWT 身份认证的正确实践与常见误区解析
java获取注册ip实例
Laravel的HTTP客户端怎么用_Laravel HTTP Client发起API请求教程
Laravel怎么进行数据库事务处理_Laravel DB Facade事务操作确保数据一致性
移动端手机网站制作软件,掌上时代,移动端网站的谷歌SEO该如何做?
如何在Windows 2008云服务器安全搭建网站?
如何快速生成专业多端适配建站电话?
Laravel Eloquent关联是什么_Laravel模型一对一与一对多关系精讲
Bootstrap整体框架之CSS12栅格系统
JavaScript如何实现继承_有哪些常用方法
手机网站制作与建设方案,手机网站如何建设?
Android仿QQ列表左滑删除操作
Swift中循环语句中的转移语句 break 和 continue
深圳网站制作平台,深圳市做网站好的公司有哪些?
PHP 500报错的快速解决方法
如何用AI帮你把自己的生活经历写成一个有趣的故事?
Laravel如何获取当前用户信息_Laravel Auth门面获取用户ID
哪家制作企业网站好,开办像阿里巴巴那样的网络公司和网站要怎么做?
动图在线制作网站有哪些,滑动动图图集怎么做?
制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?
Laravel定时任务怎么设置_Laravel Crontab调度器配置
Laravel项目怎么部署到Linux_Laravel Nginx配置详解
Laravel怎么生成二维码图片_Laravel集成Simple-QrCode扩展包与参数设置【实战】
香港服务器选型指南:免备案配置与高效建站方案解析
PHP正则匹配日期和时间(时间戳转换)的实例代码
Python制作简易注册登录系统
如何用腾讯建站主机快速创建免费网站?
中国移动官方网站首页入口 中国移动官网网页登录
Laravel如何使用Service Provider服务提供者_Laravel依赖注入与容器绑定【深度】
如何在橙子建站中快速调整背景颜色?
Android使用GridView实现日历的简单功能
Laravel如何与Inertia.js和Vue/React构建现代单页应用
标题:Vue + Vuex 项目中正确使用 JWT 进行身份认证的实践指南
如何在阿里云完成域名注册与建站?
Laravel如何与Docker(Sail)协同开发?(环境搭建教程)
Laravel怎么在Blade中安全地输出原始HTML内容


