后端并发处理的底层哲学:从“锁”到“串行与暴力”

你以为你在学锁,其实你是在学习如何面对“冲突”。

引言:一个后端的困惑

作为后端开发者,我们每天都在处理并发。我们学习乐观锁、悲观锁、分布式锁、版本号、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 扣库存,再异步落库)。

绕开的本质,是把“串行”或“暴力”操作,转移到了更高效的介质(内存)或更从容的时间(异步)去执行。

最终总结:你的后端正统心法

  1. 找资源:先看你在改数据库里的哪一行数据。
  2. 判冲突:判断这行数据在同一毫秒内被多人修改的概率。
  3. 定策略:
    • 极高冲突 + 绝不能错 → 串行(排队/悲观锁)
    • 低冲突 / 允许失败重试 → 暴力(原子操作/乐观锁)
    • 高冲突 + 允许异步 → 绕开(MQ/Redis/分流)
  4. 选实现:最后才是根据你的技术栈,选择具体的工具(MySQL 锁、Redis、RocketMQ 等)。

最后送你一句话:

写业务代码,80% 的时间不是在写 if/else,而是在用肉眼扫描“哪里会被同时改”,然后针对这个点选一种“失败后能交代得过去”的策略。

如果你在实际业务中还遇到过棘手的并发场景,不妨套用这套理论试试看。