SKIP LOCKED本身不提升性能,仅避免事务排队抢同一行;其生效需满足索引覆盖、显式事务、READ COMMITTED隔离级别及正确SQL结构,任一缺失即退化为无效语法糖。

SKIP LOCKED 本身不提升性能,它只让多个事务不再排队抢同一行——但如果你的 SQL、事务和表结构没配对,它就退化成一句无效的语法糖。
为什么加了 SKIP LOCKED 还在排队?
根本不是语法写错了,而是查询没落到“可跳过的行集合”上:
-
WHERE条件没走索引,导致全表扫描,SKIP LOCKED只能跳过“已被锁的行”,但无法跳过“本不该被扫到的行” -
ORDER BY字段无索引或存在大量重复值(比如都为'pending'),优化器每次选中不同物理行,多个事务看似跳过了彼此,实则捞出同一条业务上不可用的记录 - 事务未显式开启:
AUTOCOMMIT=1下,SELECT ... FOR UPDATE SKIP LOCKED执行完立刻释放锁,下一条UPDATE就是新事务,别人已插队 - 隔离级别为
READ UNCOMMITTED或SERIALIZABLE:READ UNCOMMITTED不支持SKIP LOCKED,SERIALIZABLE会直接报错ER_NOT_SUPPORTED_YET
真正防超卖的关键不在 SELECT,而在 UPDATE
SKIP LOCKED 只影响 SELECT 的扫描行为,它不保证你拿到的那行在 UPDATE 时还“可用”。防超卖靠的是原子性更新和应用层校验:
-
UPDATE必须带业务条件,例如WHERE id = ? AND stock > 0—— 缺了stock > 0,就是 ABA 漏洞 - 必须检查
affected rows:为 0 就说明这行已被抢先扣完,立刻失败或返回“已售罄”,不能当作成功 - 不能在一个事务里
SELECT多条再批量UPDATE:SKIP LOCKED不适用于“抢 N 个”的逻辑 - Spring 默认的
@Transactional会把整个方法包进一个事务,若中间调远程服务,锁持有时间拉长,别人更难抢到
哪些场景下 SKIP LOCKED 根本不起作用?
它不是万能锁逃逸方案,以下情况它帮不上忙:
- 热点单行更新:比如所有抢购都扣同一张优惠券的总库存(单行
coupon_id = 1),SKIP LOCKED没意义——没别的行可跳,大家还是卡在这一行上等待 - 子查询中使用:
UPDATE orders SET status = 'paid' WHERE id IN (SELECT id FROM items FOR UPDATE SKIP LOCKED)会直接报错ER_NOT_SUPPORTED_YET -
WHERE中混用函数或类型隐式转换(如WHERE CAST(id AS CHAR) = '123'),导致索引失效,退化为全表扫描 - 表结构设计不合理:所有“iPhone 15”共用一行,
SKIP LOCKED根本无从跳起——它只能跳过“已锁行”,不能跳过“不存在的行”
最容易被忽略的是:SKIP LOCKED 的有效性极度依赖事务边界控制。一旦 SELECT 和 UPDATE 被业务逻辑隔开,或者 UPDATE 条件漏掉关键校验,它就退化成一个看起来高级、实则无效的语法糖。



















