skip locked 并非自动并发加速器,仅在事务显式开启、索引覆盖、隔离级别为read committed或repeatable read且sql结构正确时生效;任一条件缺失均会导致排队、重复或空跑。

SKIP LOCKED 不是自动并发加速器,它只在事务显式开启、索引覆盖、隔离级别合适(READ COMMITTED 或 REPEATABLE READ)且 SQL 结构正确时才真正起效;漏掉任一环节,照样排队、重复或空跑。
为什么加了 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之间插入远程调用、复杂计算等耗时逻辑 → 锁持有时间拉长,别人更难抢到,也增加死锁风险
真正容易被忽略的是事务边界控制
SKIP LOCKED 的有效性极度依赖事务范围。一旦 SELECT 和 UPDATE 被业务逻辑隔开,或者 UPDATE 条件漏掉关键校验(比如没写 AND stock > 0),它就退化成一个看起来高级、实则无效的语法糖。











