SKIP LOCKED 并非加速机制,而是避免多事务争抢队列表时相互阻塞;其生效前提是查询命中索引、事务显式开启且覆盖查锁更全流程,否则退化为普通 FOR UPDATE。

SKIP LOCKED 不是用来“加速”的,而是让多个事务在抢同一张队列表时互不阻塞——前提是它真被用对了;写错一行索引或漏一个事务边界,它就退化成普通 FOR UPDATE。
为什么 SELECT ... FOR UPDATE SKIP LOCKED 还在等锁?
不是语法错了,是查询没落到“可跳过的行集合”里:
-
WHERE status = 'PENDING'没走索引 → 全表扫描 → 锁几百行,SKIP LOCKED只能跳已锁的,但大量未锁行也被加锁,后续事务仍可能撞上同一物理行 -
ORDER BY id字段无索引 → MySQL 无法稳定定位下一条可用行,多个事务反复选中同一批(尤其id有空洞或重复时) - 事务未显式开启:
AUTOCOMMIT = 1下,SELECT ... FOR UPDATE SKIP LOCKED执行完锁立刻释放,紧接着的UPDATE是新事务,别人早已插队 - 隔离级别为
SERIALIZABLE→ MySQL 直接报错ER_NOT_SUPPORTED_YET,SKIP LOCKED被禁用
真正不抢同一行的 SQL 怎么写?
必须把“查、锁、判、更”压进一个事务,并确保索引覆盖全部过滤和排序路径:
- 建联合索引时顺序关键:
CREATE INDEX idx_status_id ON jobs (status, id)——status在前才能高效过滤,id在后才支撑ORDER BY id;status单独索引不够,id单独索引也无效 - 事务第一句必须是带
SKIP LOCKED的SELECT,且只查id和必要字段,避免锁升级成间隙锁或表锁 - 拿到结果后立刻检查
id是否为空;若为空,说明无可用任务,直接COMMIT退出,不要SLEEP或重试 -
UPDATE必须用主键精确匹配 + 业务条件:UPDATE jobs SET status = 'PROCESSING' WHERE id = ? AND status = 'PENDING'—— 后置校验防 ABA,哪怕SELECT时状态是PENDING,UPDATE前也可能被别人改掉
哪些场景下 SKIP LOCKED 根本不起作用?
它解决不了单点热行竞争,只对“多行可选”的队列有效:
- 所有消费者都在抢同一张优惠券的总库存(单行
coupon_id = 1)→ 没别的行可跳,大家还是卡在这一行上等待 -
WHERE中用了函数或隐式转换,比如WHERE DATE(created_at) = '2026-06-15'→ 索引失效,退化为全表扫描 + 大量无效加锁 - 子查询中使用:
UPDATE tasks SET done = 1 WHERE id IN (SELECT id FROM jobs FOR UPDATE SKIP LOCKED)→ 直接报错ER_NOT_SUPPORTED_YET - Spring 的
@Transactional把整个方法包进事务,中间夹着远程调用或耗时逻辑 → 锁持有时间过长,SKIP LOCKED失去意义
最常被忽略的一点:SKIP LOCKED 的效果完全依赖于查询是否命中索引+事务是否真正包裹住后续更新。一旦其中任一环节断开,它就只是个心理安慰。


















