连载中 16/20

不用锁行不行:四个替代方案的正确打开方式

2026-07-03 · 4595 阅读 · 0 评论 · 0 赞

加锁之前的三秒钟

前十五篇教你把锁用好,这一篇先劝你慢点用。凡是「我要抢锁」的念头冒出来,先回答一个问题:互斥的对象到底是「一行数据」还是「一段逻辑」?如果答案是前者——你保护的是某个账户余额、某条任务记录、某个订单状态——那大概率不需要分布式锁:数据库自带的并发控制(乐观锁、唯一索引、行锁)就是为这事生的,它们运行在数据的属地,没有锁服务、没有 TTL、没有看门狗,比任何分布式锁都便宜且可靠。分布式锁真正的领地是后者:保护一段横跨多个资源、多台机器的执行逻辑。本篇把四个替代方案排成纵队,逐个看适用与代价。

方案一:乐观锁版本号

第 1 篇的扣款竞态,最便宜的解法是给账户表加 version 字段:

-- 读余额与版本
SELECT balance, version FROM account WHERE card_no = '8801';
-- 应用层判断余额充足后,带版本号更新
UPDATE account SET balance = balance - 80, version = version + 1
WHERE card_no = '8801' AND version = 17;
-- 影响行数 = 0:版本已被别人推进,余额判断作废,读新值重试

并发冲突时「后到者作废重试」,没有锁服务、没有超时问题、天然防死锁(失败即返回,不等待)。代价是高冲突下的重试风暴——一百个请求更新同一行,九十九个要重试,读放大明显。适合冲突率低的行级更新;冲突率高的(热点账户),见 MySQL 系列第 17 篇的缓冲记账,或直接上队列。

方案二:唯一索引

「防止重复做某件事」类需求(重复发券、重复建单、重复执行任务),唯一索引是终极答案:用业务键做唯一约束,重复操作在数据库层直接被拒,原子性由存储引擎背书,竞态窗口为零——分布式事务系列第 10 篇把它称为「最后的墙」。定时任务防重跑的变体是「抢占更新」:

UPDATE task SET status = 'RUNNING', owner = 'node-2'
WHERE id = 42 AND status = 'PENDING';
-- 影响行数 = 1:我抢到了;= 0:别人在跑或跑过了

三台实例同一秒执行同一条 UPDATE,数据库行锁保证串行,只有一个人的影响行数是 1——不用抢任何锁,任务执行权就分配好了。老王三家门店的当班调度,用的就是这招。

方案三:行锁 select for update

读和写必须一起串行的场景(读余额、判断、扣款三步原子化),悲观行锁一步到位:事务里 select ... for update 锁住该行,判断与更新在同一事务内完成,其他事务阻塞等待。它把第 1 篇的竞态窗口焊死了,代价也明码标价:持锁期间数据库连接被占用,长事务拖垮连接池;锁粒度大时死锁风险上升。适合「事务短、冲突集中在确定行」的场景;跨库跨服务的逻辑,行锁鞭长莫及,那才是分布式锁的活。

方案四:队列串行化

高频写同一资源的终极方案是干脆取消并发:所有变更请求进消息队列,同一分区/队列的消息串行消费——并发问题变成了排队问题,锁彻底消失。消息队列系列第 6 篇的顺序消息在此派上用场:按账户 ID 分区,同一账户的消息落同一分区,消费端单线程处理,余额更新天然有序。代价是响应延迟(异步化,用户等不到同步结果)与吞吐规划(分区数、消费者数);秒杀扣库存、账务流水这类「结果可以异步、正确性必须满分」的场景是它的主场。

方案适用代价
乐观锁版本号低冲突的行级更新高冲突下重试风暴
唯一索引 / 抢占更新防重复、任务认领只覆盖「一次事」,不保护逻辑段
行锁 for update短事务的读改写原子化占连接、死锁风险
队列串行化高频写的异步化收敛响应延迟、架构改造

决策口诀收拢:防重复用唯一索引,改数据用乐观锁,短事务用行锁,高并发用队列;跨资源跨服务的逻辑段,才轮到分布式锁。下一篇讲锁正确性的最后一块拼图——fencing token:当锁的持有者是个幽灵,下游凭什么拒绝它。

503

10 年全栈工程师 · 503咖啡馆主理人

#乐观锁#唯一索引#行锁#队列串行化#替代方案#select for update

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 4 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞