游乐游手机版
首页/数据库/文章详情

MySQL事务如何解决电商库存超卖问题与并发控制

时间:2026-08-24 13:00
普通 UPDATE 容易发生库存超卖,根本原因在于“读-判断-写”不是原子操作:两个并发请求同时读取到 stock=1,随后都执行减 1,最终就可能把库存扣成 -1;而 WHERE stock>0 属于快照读,不会加锁,因此无法阻塞其他事务的并发更新。为什么普通 UPDATE 容易导致库存超卖在电商

普通 UPDATE 容易发生库存超卖,根本原因在于“读-判断-写”不是原子操作:两个并发请求同时读取到 stock=1,随后都执行减 1,最终就可能把库存扣成 -1;而 WHERE stock>0 属于快照读,不会加锁,因此无法阻塞其他事务的并发更新。

如何用MySQL事务解决电商库存超卖问题

为什么普通 UPDATE 容易导致库存超卖

在电商系统下单场景中,如果只是简单执行 UPDATE product SET stock = stock - 1 WHERE id = 123 AND stock > 0,表面上看已经做了库存判断,但在高并发条件下,依然可能出现超卖问题。核心原因是:读取库存和更新库存不是一个不可分割的原子过程,两个请求可能同时读到 stock=1,然后先后执行减 1,最终导致库存被扣成 -1。

  • MySQL 默认隔离级别为 REPEATABLE READ,无法彻底避免这种“读-改-写”带来的并发竞态问题
  • WHERE stock > 0 属于快照读,不加行锁,不能阻止其他事务同时修改库存
  • 即使是单条 UPDATE,也是在条件满足后才执行,条件判断与真正更新之间仍然存在并发窗口

用 SELECT ... FOR UPDATE 显式加锁

如果在事务中先查询库存再进行扣减,就必须通过加行锁来保证同一条商品记录不会被多个事务同时修改:

START TRANSACTION;
SELECT stock FROM product WHERE id = 123 LOCK IN SHARE MODE; -- ❌ 错误:共享锁不阻止其他事务 UPDATE
SELECT stock FROM product WHERE id = 123 FOR UPDATE; -- ✅ 正确:排他锁,阻塞其他事务对这行的读写
-- 检查 stock >= 1,再执行 UPDATE
UPDATE product SET stock = stock - 1 WHERE id = 123;
COMMIT;
  • FOR UPDATE 必须放在事务中使用,并且 WHERE 条件要命中索引(如主键或唯一索引),否则可能退化为表级锁
  • 如果 WHERE 条件没有走索引,MySQL 可能锁住整个聚簇索引,严重影响数据库并发性能
  • 不要先在应用层判断库存再执行 UPDATE;正确做法是先锁定记录,再判断库存,再扣减,否则加锁没有实际意义

更安全的单 SQL 原子扣减方案

为了避免应用层先查后改带来的并发风险,更推荐把库存校验和库存扣减合并为一条 SQL,由 MySQL 直接保证原子性:

UPDATE product 
SET stock = stock - 1 
WHERE id = 123 AND stock >= 1;
  • 执行完成后检查 ROW_COUNT():返回 1 表示扣减成功;返回 0 则说明库存不足或库存已经被其他请求扣完
  • 这条 SQL 自身就是原子操作,理论上不一定需要额外事务包裹,但实际业务中仍建议放入事务,便于后续创建订单、写日志等操作统一提交
  • 需要注意:stock >= 1 属于当前读,会触发隐式加锁(InnoDB 的 next-key lock),整体效果接近 FOR UPDATE,但实现方式更简洁
  • 不要写成 stock > 0,对于整数库存来说,最小合法值就是 0,使用 >= 1 在语义上更明确,也更符合库存扣减场景

事务失败后如何重试与幂等

即便使用了事务加锁或原子更新,实际线上环境中仍然可能因为网络抖动、死锁或连接断开导致事务失败,因此必须设计好重试机制与幂等控制:

  • 捕获 MySQL 错误码:1213(Deadlock)、2006(MySQL server has gone away)、2013(Lost connection)这类错误通常需要重试
  • 重试前应加入随机短暂延迟(例如 10–100ms),避免大量请求同时重试造成雪崩
  • 订单号必须保证全局唯一,插入订单表时可使用 INSERT ... ON DUPLICATE KEY UPDATE 或唯一索引约束,防止重复创建订单
  • 不要依赖“先查订单是否存在,再决定是否插入”的方式——查询加插入不是原子操作,正确方式是直接插入,利用唯一键做幂等拦截

真正困难的地方,并不只是把那条 UPDATE 写对,而是要准确理解锁的范围、掌握 FOR UPDATEUPDATE ... WHERE 在不同索引命中情况下的差异,以及在分布式架构中如何与 Redis 库存预扣减协同配合。这些数据库并发控制细节一旦处理不当,往往只有在压测或生产高峰期才会暴露问题。

来源:https://www.php.cn/faq/3020979.html
上一篇MySQL查询是否命中缓存的判断方法 下一篇MySQL 5.7与8.0版本如何选择更合适
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Redis是什么:核心特性、架构与应用场景解析
数据库 · 2026-09-01

Redis是什么:核心特性、架构与应用场景解析

Redis是一款基于内存的键值型NoSQL数据库,以超高读写速度和丰富的数据结构著称。本文系统梳理Redis的核心特性、架构组成、性能优势及典型应用场景,并通过与Memcached、MySQL、MongoDB的对比,帮助开发者快速判断Redis是否适合当前业务需求。

Windows 安装 MongoDB 完整图文教程
数据库 · 2026-09-01

Windows 安装 MongoDB 完整图文教程

本文详细介绍在 Windows 系统上安装 MongoDB 的完整流程。从官网下载 MSI 安装包开始,逐步演示自定义安装路径、配置 Windows 服务、跳过 MongoDB Compass 等关键选项,并提供通过系统服务列表验证安装是否成功的方法,帮助开发者快速搭建本地 MongoDB 环境。

Linux 安装 MongoDB 完整指南:依赖配置、环境变量与服务启动
数据库 · 2026-09-01

Linux 安装 MongoDB 完整指南:依赖配置、环境变量与服务启动

本文详解在 Linux 系统下安装 MongoDB 的完整流程,涵盖依赖包安装、二进制包下载解压、环境变量配置、数据与日志目录创建及服务启动验证。通过标准化命令与路径说明,帮助开发者快速完成部署并确认服务状态。

MacOS安装MongoDB完整教程
数据库 · 2026-09-01

MacOS安装MongoDB完整教程

本文介绍在MacOS系统下安装MongoDB的完整流程,涵盖下载、解压、目录配置、环境变量设置及服务启动。通过明确的命令与参数说明,帮助开发者快速完成环境搭建并验证安装结果。

Ubuntu系统安装与配置Redis完整指南
数据库 · 2026-09-01

Ubuntu系统安装与配置Redis完整指南

本文详解在Ubuntu系统中安装Redis的两种主流方式:apt在线安装与源码编译安装。涵盖版本选择逻辑、服务启停与状态检查、连接验证方法,以及在线练习工具与桌面GUI客户端的对比与使用建议,帮助开发者快速搭建并验证Redis运行环境。