你以为你在学锁,其实你是在学习如何面对“冲突”。
引言:一个后端的困惑
作为后端开发者,我们每天都在处理并发。我们学习乐观锁、悲观锁、分布式锁、版本号、CAS、MQ 异步……但这些概念之间到底是什么关系?它们有没有一个统一的底层逻辑?
在和一位读者深度交流后,我们共同梳理出了一套极简的认知框架。这套框架剥离了所有中间件的具体实现,直指并发控制的本质。读完这篇文章,你将不再纠结“用什么锁”,而是直接洞悉“该用什么策略”。
在进入正题之前,先立一个更底层的世界观,它是一切讨论的前提:
锁、原子操作、唯一约束——这些能力绝大多数是数据库已经实现好的。你的工作不是实现它们,而是选择和调用。 需要回滚就开事务,需要防重就用唯一索引,需要原子扣减就写条件更新。不要在自己代码里“造”并发控制,除非你正在写数据库本身。这一点想清楚,后面所有策略都只是“怎么调用”的问题。
第一章:一切并发问题的根源——资源与冲突
不管业务多复杂,落到数据库层面,无非是对某一行数据的“读”与“写”。
请记住这个起点:所有并发控制问题,都始于两个问题——
- 我在操作什么资源?(库存、余额、用户资料、订单状态)
- 这个资源会在同一时刻被多个请求同时修改吗?(即:冲突概率有多高?)
结论:后端编码的核心,本质上是“找出冲突点,然后选择一种解决冲突的策略”。
第二章:解决并发冲突的两大终极武器
当我们审视所有解决冲突的技术(锁、队列、版本号、原子操作)后,剥去所有外壳,你会发现底层只有两种解法:
武器一:改成串行(对应悲观锁、排队)
- 做法:让所有请求排队,一次只放一个进去操作。
- 代价:牺牲了时间(等待),换来了绝对的正确性。
- 适用:冲突极高,且不允许任何失败的业务。
- 技术实现:数据库
SELECT ... FOR UPDATE、Redis 分布式锁、消息队列单线程消费。
武器二:暴力硬算(对应乐观锁、原子操作)
先说清楚:这里的“暴力”不是暴力破解,而是不排队、不协调,让大家一起上,靠数据库/Redis 的单行原子性兜底。
- 做法:不排队,大家一起上。利用数据库或 Redis 的单行原子操作直接计算结果。
- 代价:可能会失败(版本号不匹配、插入冲突),需要业务层重试;但不阻塞其他请求。
- 适用:冲突较低,或偶尔失败重试对用户无感的业务。
- 技术实现:
UPDATE SET count = count + 1、带version的条件更新、带唯一索引的INSERT。
黄金法则
- 优先用原子语句/条件更新:单条 SQL 能完成的——比如防超卖的
UPDATE stock SET count=count-1 WHERE id=? AND count>0,或带 version 的乐观更新——别拆成“先查后改”。 - 必须“先查后改”时:要么排队(串行),要么带条件重试(暴力)。
- 注意:多语句的业务规则,该开事务还是开事务。这里说的不是“不用事务”,而是“别为了一个本可以原子完成的操作,去手动加锁”。
第三章:普通程序员 vs. 架构师的思维分水岭
| 思维层次 | 看到需求时的反应 | 决策依据 |
|---|---|---|
| 普通程序员 | “这个接口我该用乐观锁还是悲观锁?” | 记忆工具用法,凭感觉选型 |
| 架构师视角 | “这个业务的数据冲突概率有多高?该串行还是暴力?” | 先定策略(串行/暴力/绕开),再选实现工具(锁/队列/版本号) |
架构师从不先想工具。他们先看清冲突的本质,再让工具为决策服务。
第四章:典型业务场景实战决策
| 业务场景 | 冲突核心 | 决策 | 理由 |
|---|---|---|---|
| 秒杀扣库存 | 库存行被万人争抢 | 绕开为主 + 串行兜底:Redis DECR 原子扣减 → 异步落库;数据库侧条件更新兜底防超卖 |
流量先绕到内存,最终一致性可接受 |
| 银行转账 | 账户余额同时被多人操作 | 串行(FOR UPDATE) |
金钱不容忍任何错误 |
| 社交点赞/取消赞 | 同一用户重复点赞,防止重复插入 | 暴力(唯一索引) | 冲突概率低,依赖数据库约束即可 |
| 更新用户昵称 | 几乎无冲突 | 暴力(直接 UPDATE) |
没必要加锁,浪费性能 |
| 商品浏览数/点击量 | 高频累加,但不要求强一致 | 暴力(UPDATE SET views=views+1) |
+1 是原子操作,不需额外锁 |
第五章:架构设计的最高境界——如何“绕开”冲突?
除了“串行”和“暴力”,还有第三种思路:绕开。
本质:既然这个点堵,我就不让流量打到这个点上。
实现手段:
- 分流:分库分表,把数据拆散到不同节点,降低单表冲突。
- 异步化:把强一致请求变成最终一致性(如秒杀先返回“排队中”,后台慢慢扣库存)。
- 转移战场:用 Redis 的单线程原子操作代替数据库锁(如用 Redis
DECR扣库存,再异步落库)。
绕开的本质,是把“串行”或“暴力”操作,转移到了更高效的介质(内存)或更从容的时间(异步)去执行。
最终总结:你的后端正统心法
- 找资源:先看你在改数据库里的哪一行数据。
- 判冲突:判断这行数据在同一毫秒内被多人修改的概率。
- 定策略:
- 极高冲突 + 绝不能错 → 串行(排队/悲观锁)
- 低冲突 / 允许失败重试 → 暴力(原子操作/乐观锁)
- 高冲突 + 允许异步 → 绕开(MQ/Redis/分流)
- 选实现:最后才是根据你的技术栈,选择具体的工具(MySQL 锁、Redis、RocketMQ 等)。
最后送你一句话:
写业务代码,80% 的时间不是在写 if/else,而是在用肉眼扫描“哪里会被同时改”,然后针对这个点选一种“失败后能交代得过去”的策略。
如果你在实际业务中还遇到过棘手的并发场景,不妨套用这套理论试试看。