在秒杀实战中如何利用MySQL行级锁防止库存出现负数?

千磊吖_1675

千磊吖_1675

2026-11-06

465人浏览

原创

最稳的原子扣减写法是UPDATE seckill_goods SET stock = stock - 1 WHERE id = 123 AND stock >= 1,它通过单条SQL天然实现库存检查与扣减的原子性,具备行锁、原子性、无竞态三重保障,无需显式事务或FOR UPDATE,且必须检查影响行数判断是否成功。

在秒杀实战中如何利用mysql行级锁防止库存出现负数?

UPDATE WHERE stock >= 1 是最稳的原子扣减写法

直接用一条 UPDATE 完成“检查库存是否充足 + 扣减”两个动作,是防止负库存最简单、最可靠的方式。它天然具备行锁、原子性、无竞态三重保障,不需要显式事务或 FOR UPDATE,也不依赖隔离级别。

常见错误是把“查库存”和“更新库存”拆成两步,比如先 SELECT stock FROM seckill_goods WHERE id = 123,再在应用层判断 if stock > 0,最后执行 UPDATE。这中间的判断逻辑完全脱离数据库锁保护,高并发下必然超卖。

  • UPDATE seckill_goods SET stock = stock - 1 WHERE id = 123 AND stock >= 1 —— 成功时影响行数为 1,失败(库存不足或记录不存在)时为 0
  • 必须检查 rowcount 或 MySQL 的 ROW_COUNT() 返回值,不能只看 SQL 是否执行成功
  • 该语句在 InnoDB 中自动对匹配的行加排他锁(X 锁),其他并发请求会排队等待或立即失败,不会读到旧值再覆盖
  • 如果 id 是主键或唯一索引,锁粒度严格为单行;若条件不走索引(如 WHERE sku = 'xxx' 且未建索引),可能升级为表锁,务必用 EXPLAIN 验证

SELECT FOR UPDATE 必须搭配显式事务和索引查询

当业务需要在扣减前做额外校验(比如检查用户限购、活动状态、库存预警等),就得先读再判再写,这时必须用 SELECT ... FOR UPDATE,但极易踩坑。

典型失效场景:事务自动提交开启(autocommit=1)、查询条件没走索引、FOR UPDATE 和后续 UPDATE 不在同一个事务里。

  • 连接必须设为 autocommit=0,且显式执行 BEGIN 或 START TRANSACTION
  • SELECT stock FROM seckill_goods WHERE id = 123 FOR UPDATE ✅(主键查询,锁单行)
  • SELECT stock FROM seckill_goods WHERE sku = 'ABC' FOR UPDATE ❌(sku 无索引 → 全表扫描 → 表锁或大量行锁)
  • 事务中不能调外部 HTTP 接口,网络延迟会导致锁持有时间不可控,拖垮整个链路
  • 必须设置合理的 innodb_lock_wait_timeout(默认 50 秒),并在应用层捕获 Lock wait timeout exceeded 错误,及时降级或重试

unsigned + where 条件可兜底防负数,但不能替代原子扣减

给库存字段加 unsigned 属性,配合 WHERE stock >= 1,能在数据库层面阻止负值写入,但这只是“最后一道闸”,不是并发安全方案。

MySQL8 CRM Schema Design Expert
MySQL8 CRM Schema Design Expert

面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。

下载

它解决的是“写入后发现负了”的补救问题,而不是“根本不会写负”的预防问题。一旦发生超卖,UPDATE 可能因违反 unsigned 约束而报错,但此时已有部分请求成功扣减,库存已失真。

  • ALTER TABLE seckill_goods MODIFY COLUMN stock INT UNSIGNED; 是必要但不充分的防护
  • 仅靠 unsigned 无法防止多个请求同时通过 WHERE stock >= 1 判断后都执行减法——因为判断和减法仍非原子
  • 真正起作用的仍是 WHERE stock >= 1 这个条件本身,unsigned 只是让失败更早暴露,避免静默负数
  • 如果业务允许“超卖后拦截”,可用此组合做快速失败;但强一致性要求下,必须依赖原子 SQL 或锁机制

别迷信 SERIALIZABLE 隔离级别

SERIALIZABLE 看似最强,但它对普通 SELECT 加共享锁,对 UPDATE 加排他锁,前提是所有操作都在同一事务内完成。现实中,很多 ORM 默认每条语句自动提交,或者把“查库存”和“生成订单”拆成两个事务,这时 SERIALIZABLE 就完全失效。

它还会显著降低并发能力,因为读操作也要加锁,容易引发锁等待甚至死锁,实际压测中 QPS 往往比 REPEATABLE READ 低一个数量级。

  • 不要指望靠改隔离级别解决超卖,而应聚焦于操作是否原子、事务边界是否合理
  • REPEATABLE READ(MySQL 默认)配合正确的 UPDATE ... WHERE 或 SELECT ... FOR UPDATE 已足够
  • 若真要用 SERIALIZABLE,必须确保整个秒杀流程(查、判、扣、下单)在一个事务里,且连接不自动提交
  • 线上环境建议保持默认隔离级别,优先优化 SQL 和事务结构,而非盲目调高隔离等级

真正难的不是写对一行 SQL,而是确认那行 SQL 在你当前的索引结构、事务配置、连接模式下,确实只锁住了你想锁的那一行。每次上线前,用 EXPLAIN 看执行计划,用 SHOW ENGINE INNODB STATUS 查锁信息,比背原理管用得多。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

mysql

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

2023.10.12

4783

8

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.27

971

4

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

2024.02.23

1249

5

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

2024.03.06

6801

10

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

2024.03.06

3503

4

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

2024.04.07

6780

11

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

2024.04.29

9421

6

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

2024.04.29

1270

5

sql中删除一列的命令是什么
sql中删除一列的命令是什么

在sql中,使用alter table语句可以删除一列,语法为:alter table table_name drop column column_name。想了解更多sql的相关内容,可以阅读本专题下面的文章。

2024.04.29

1112

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL vs MySQL
PostgreSQL vs MySQL

共1课时 | 194人学习