skip locked 本身不提升抢单性能,仅避免事务排队等待同一行,防超卖需依赖原子性update(如where id=? and stock>0)及affected rows校验,否则仍会超卖或空跑。

SKIP LOCKED 本身不提升抢单性能,它只是让多个事务“不排队等同一行”,但能否真正防超卖、扛住高并发,取决于你后续怎么 UPDATE。
为什么 SKIP LOCKED 在秒杀里常被误用
很多人以为写个 SELECT ... FOR UPDATE SKIP LOCKED 就万事大吉,结果压测一上就超卖或返回空。根本问题在于:
- SKIP LOCKED 只影响当前 SELECT 语句的扫描行为,它不保证你拿到的那行在 UPDATE 时还“可用”
- 如果 SELECT 出
id=123,再执行UPDATE seckill_goods SET stock = stock - 1 WHERE id = 123(没带stock > 0条件),那就等于裸奔——其他事务可能刚把它扣到 0,你还继续减,直接负库存 - Spring 默认的
@Transactional会把整个方法包进一个事务,导致 SELECT 和 UPDATE 中间夹着业务逻辑(比如调远程积分服务),锁持有时间拉长,别人更难抢到
必须配合原子性 UPDATE 才有效
SKIP LOCKED 的价值,只在和“单行 + 条件更新”绑定时才成立。核心是靠 MySQL 行级更新的原子性兜底:
- SELECT 必须用主键或唯一索引精确命中(如
WHERE id = ?),不能靠WHERE status = 'available'这类范围扫描,否则 SKIP LOCKED 效果极差 - UPDATE 必须重复校验业务条件,例如
UPDATE ... SET stock = stock - 1 WHERE id = ? AND stock > 0—— 缺了stock > 0,就是 ABA 漏洞 - 应用层必须检查
affected rows:为 0 就说明这行已被抢先扣完,立刻重试或返回“已售罄”,不能当作成功 - 不要在一个事务里 SELECT 多条再批量 UPDATE,SKIP LOCKED 不适用于“抢 N 个”的逻辑
它不是并发银弹,而是热点解耦工具
SKIP LOCKED 解决不了热行本身的竞争,它只是把“排队等锁”变成“各抢各的”。真实瓶颈往往在别处:
- QPS 超过 2000 后,即使用了 SKIP LOCKED,大量线程也会空跑(查到行但 UPDATE 返回 0),此时比加锁还耗 CPU
- 表结构设计不合理(比如所有“iPhone 15”共用一行)时,SKIP LOCKED 根本无从跳起——它只能跳过“已锁行”,不能跳过“不存在的行”
- 没配
innodb_lock_wait_timeout或连接池超时,失败事务仍会卡住连接,拖垮整个数据库
真正容易被忽略的是:SKIP LOCKED 的有效性极度依赖事务边界控制。一旦 SELECT 和 UPDATE 被业务逻辑隔开,或者 UPDATE 条件漏掉关键校验,它就退化成一个看起来高级、实则无效的语法糖。











