skip locked 不是任务队列,需配合事务、索引与原子更新;卡住主因是where/order by未走索引、autocommit=1、隔离级别不当;正确用法是单事务内select for update skip locked+主键update,并建好联合索引。

SKIP LOCKED 本身不构成任务队列,它只是让多个事务能并发“抢到不同行”——真正可用的队列必须靠事务边界、索引设计和原子更新共同兜底。
为什么直接写 SELECT ... FOR UPDATE SKIP LOCKED 还是会卡住
这不是语法错误,而是查询没落到可跳过的行集合上:
-
WHERE条件没走索引 → MySQL 全表扫描,锁住大量无关行,SKIP LOCKED只能跳“已被锁的行”,跳不掉“本不该扫到的行” -
ORDER BY字段无索引或值高度重复(比如全是'PENDING')→ 优化器每次选中物理位置不同的行,多个事务看似跳过彼此,实则捞出同一条业务上已失效的记录 - 事务未显式开启(
AUTOCOMMIT=1)→SELECT ... FOR UPDATE SKIP LOCKED执行完立刻释放锁,后续UPDATE是新事务,别人早已插队 - 隔离级别为
READ UNCOMMITTED或SERIALIZABLE→ 前者不支持SKIP LOCKED,后者直接报错ER_NOT_SUPPORTED_YET
怎么写出真正不冲突的消费 SQL
必须把“查、锁、判、更”压进一个事务,并确保索引覆盖全部过滤和排序路径:
- 建联合索引时字段顺序不能错:例如
CREATE INDEX idx_status_id ON jobs (status, id),status在前才能高效过滤WHERE status = 'PENDING',id在后才支撑ORDER BY id - 事务第一句必须是带
SKIP LOCKED的SELECT,且只查主键和必要字段,避免锁升级 - 拿到结果后立刻检查
id是否为空;若为空,说明无可用任务,直接COMMIT退出,不要重试或sleep -
UPDATE必须用主键精确匹配,并复核业务状态:UPDATE jobs SET status = 'PROCESSING' WHERE id = ? AND status = 'PENDING'—— 防 ABA 场景下误更新已变更状态的记录
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 DATE(created_at) = '2026-06-06')→ 导致索引失效,退化为全表扫描 - 应用层在
SELECT和UPDATE之间插入远程调用、复杂计算等耗时逻辑 → 锁持有时间拉长,别人更难抢到,也增加空跑概率
最常被忽略的一点:ORDER BY 字段必须有索引,否则 LIMIT 1 可能选中任意行,SKIP LOCKED 失效。这不是性能问题,是语义崩塌——你根本不知道自己“跳过”了谁、“抢到”了谁。











