当前位置:首页 > 云谷精选

MySQL悲观锁和乐观锁在并发控制中怎么选

admin2026年03月18日云谷精选17.16万
摘要:# 悲观锁还是乐观锁?MySQL并发控制,别让“锁”事拖垮你的系统 前两天帮一个做电商的朋友看他们的大促预案,发现他们库存扣减那块儿,清一色用的`SELECT ... FOR UPDATE`(也就是悲观锁)。我问他们为啥这么选,技术负责人一脸理所当然:“…

悲观锁还是乐观锁?MySQL并发控制,别让“锁”事拖垮你的系统

前两天帮一个做电商的朋友看他们的大促预案,发现他们库存扣减那块儿,清一色用的SELECT ... FOR UPDATE(也就是悲观锁)。我问他们为啥这么选,技术负责人一脸理所当然:“并发控制嘛,肯定要锁住啊,不然数据不就乱了?”

这场景你应该不陌生吧?很多团队在面临高并发数据竞争时,第一反应就是“上锁”。但说实话,很多所谓的“稳妥方案”,上线后真遇到流量洪峰,可能比不锁还容易出问题——不是超卖,就是直接把数据库连接池打满,整个服务雪崩。

我自己看过不少生产事故,问题往往不是没上防护,而是锁用错了地方,或者选错了类型。今天咱们就抛开那些教科书定义,聊聊MySQL里悲观锁和乐观锁到底该怎么选。这可不是二选一的考试题,而是一个直接关系到你系统能不能扛住618、双十一的实战问题。

一、先搞明白它俩到底在干啥(说人话版)

别被名字唬住。悲观锁和乐观锁,核心区别就一点:它们对待“冲突”的态度截然不同。

悲观锁像个疑心很重的保安。它的逻辑是:“我觉得肯定会有人跟我抢(数据冲突概率很高),所以我要先占住,谁也别想动。” 在MySQL里,你通常用SELECT ... FOR UPDATE或者SELECT ... LOCK IN SHARE MODE来实现。一旦你给某行数据上了这把锁,在事务提交前,其他想改这行数据的事务都得乖乖排队等着。

这听起来很安全,对吧?但代价是性能并发度。想象一下早高峰只有一个闸机的地铁站。

乐观锁则像个心态开放的协调员。它觉得:“大家应该不会总撞到一起吧(冲突概率低)?那咱们先各自改,改完提交的时候再看看有没有冲突。” 它不真的去“锁”数据,而是给数据加个“版本号”(比如一个version字段,或者用时间戳,甚至数据本身的某些哈希值)。你读数据的时候把版本号记下来,更新的时候,必须带上这个版本号,并且检查“我更新的时候,这数据的版本号还是不是我刚才读到的那个”。如果不是,说明有人在我修改期间动了数据,那这次更新就失败,业务上通常选择重试。

说白了,悲观锁是 “先防后改”,乐观锁是 “先改后验”

二、什么时候该用悲观锁?(别犹豫,就这些场景)

别一听乐观锁时髦就无脑上。悲观锁在下面这些场景里,依然是“定海神针”。

1. 冲突是常态,而不是意外。 如果你的业务逻辑决定了一行数据被频繁争用是日常状态,那用乐观锁会是一场灾难。比如一个热门商品的唯一库存、一个全局配置项的开关、一个需要严格递增的序列号生成器。在这些场景下,乐观锁会导致大量的更新失败和重试,重试本身也是消耗,还可能引发“惊群效应”。这时候,用悲观锁虽然会让请求排队,但顺序是可控的,结果是确定的,反而更简单可靠。

我见过一个票务系统,对最前排的几张“黄金座”用了乐观锁,结果开售瞬间,99%的请求都在不停重试抢版本号,数据库CPU直接飙满,还不如老实排队。

2. 重试成本极高,或根本无法重试。 有些业务操作是“一锤子买卖”,失败了不能简单地再来一次。比如,从你的账户余额里扣一笔钱,如果采用乐观锁更新失败,难道能对用户说“抱歉扣款冲突了,请您再付一次”吗?显然不行。这类涉及核心资产、状态必须绝对准确的场景,用悲观锁一把锁住,保证原子性,避免后续所有复杂的补偿和回滚逻辑,往往是更经济的选择。

3. 你要操作的数据,本身就需要被锁定来保证一组操作的绝对串行。 这有点绕。举个例子,你需要先读A,再根据A的值决定怎么改B,最后还要更新A。这一连串操作必须作为一个不可分割的整体。如果中间A被其他人改了,你的整个逻辑就乱套了。这时候,在事务一开始就用悲观锁锁住A,是最清晰、最不容易出错的写法。虽然这可能影响一点并发,但换来了逻辑的简洁和正确性。

一句话总结:当你“伤不起”或者“肯定要打架”的时候,选悲观锁。

三、什么时候该拥抱乐观锁?(这才是性能提升的关键)

乐观锁被大规模应用,尤其是在互联网高并发场景下,不是没有道理的。它的好处太明显了:避免锁等待,提升系统的整体吞吐量。数据库连接是宝贵的,不让线程阻塞在锁上,它们就能去服务其他请求。

下面这些情况,你认真考虑一下乐观锁:

1. 读多写少,这是乐观锁的天堂。 大部分系统都是读操作远多于写操作。比如新闻详情页、商品信息页。即便有写操作,冲突的概率也很低。在这种场景下,你为那1%可能的冲突,让99%的读操作都去走一遍加锁解锁的流程(即使是共享锁),简直是巨大的浪费。用乐观锁,读操作完全无锁,畅行无阻,体验极佳。

2. 写冲突确实不频繁。 哪怕是一个写操作较多的业务,如果数据维度设计得好,冲突也能被稀释。比如,不是把100个商品的库存都放在一行记录里,而是拆分成100行,每个商品ID独立一行。这样用户A买商品1,用户B买商品2,根本不会冲突。在这种情况下,乐观锁的更新失败率会很低,重试机制完全能 cover 住。

3. 应用层能很好地处理“更新失败”。 这是使用乐观锁的前提。你的业务代码不能假设更新一定成功,必须准备好面对UPDATE ... WHERE version = xxx返回影响行数为0的情况。然后,你需要决定:是立刻重试(适合短时冲突)?是返回给用户一个友好提示(如“信息已过期,请刷新”)?还是进入一个更复杂的冲突解决流程? 如果你的团队有这种设计和编码意识,乐观锁会是一个强大的武器。

4. 追求极致的系统吞吐量。 在一些秒杀、抢购的极致场景下,有一种“乐观锁+库存预扣”的混合玩法。先在应用层用Redis或内存计数器做一层“乐观”的库存校验和预减,最后到数据库用乐观锁做最终扣减。这相当于把大部分冲突拦截在数据库之外,数据库层只是做最终的一致性保障,承受的压力小了很多。

四、别走极端:混合使用与实战心法

看到这里,你可能觉得:“懂了,我的系统大部分是读,所以全上乐观锁!” 打住,这才是最危险的想法。

一个成熟的系统,往往是悲观锁和乐观锁的混合体。 关键就在于 “按场景细分”

  • 用户账户余额、核心订单状态 -> 优先考虑悲观锁。钱的事,稳字当头。
  • 商品库存(非极度热门) -> 可以大胆用乐观锁。配合库存分桶(一个SKU拆成多个库存记录),效果更好。
  • 文章点赞数、阅读数 -> 甚至可以用更“乐观”的办法,比如Redis累加再定时同步回DB,或者直接用UPDATE table SET count = count + 1(这其实是一种特殊的乐观锁)。
  • 配置信息、全局开关 -> 悲观锁,或者利用MySQL的写锁(FOR UPDATE)保证读到的绝对是最新值。

几个实战中容易踩的坑:

  1. 长事务 + 悲观锁 = 灾难。如果你用悲观锁锁住一行数据,然后这个事务里还在调外部API、处理复杂业务逻辑,十几秒不提交,其他所有相关请求全堵死。用悲观锁,事务一定要短平快。
  2. 乐观锁的“版本号”选不对。用version字段是最清晰的。别用update_time,因为时间精度可能有问题,不同机器也可能有时钟差。
  3. 无脑重试。乐观锁更新失败,如果只是简单循环重试,可能会在热点数据上造成恶性循环。要加指数退避,要设重试上限,甚至可以考虑把失败请求丢到队列里异步慢慢处理。
  4. 忘了“读已提交”隔离级别的影响。在默认的“可重复读”级别下,普通的SELECT看不到别的事务已提交的修改。这可能会让你误判冲突概率。根据业务需要,有时可能需要调整隔离级别。

写在最后

选择悲观锁还是乐观锁,本质上是在 “数据绝对安全”“系统并发性能” 之间做权衡。没有银弹。

我的建议是,下次设计数据访问层时,别凭感觉。先压测:模拟真实并发场景,看看冲突率到底有多高。再评估:这个业务能不能接受更新失败和重试。最后做决定

技术选型就像选工具,锤子再好,也不能用来拧螺丝。把悲观锁用在它该用的地方,那是稳健;把乐观锁用在适合它的场景,那叫智慧。

如果你的源站还在所有并发场景下无脑FOR UPDATE,看完这篇文章,你心里应该已经有答案了吧?行了,思路就聊这么多,具体代码怎么写,怎么调优,那就是另一个故事了。

扫描二维码推送至手机访问。

版权声明:本文由www.ysyg.cn发布,如需转载请注明出处。

本文链接:http://www.ysyg.cn:80/?id=393

“MySQL悲观锁和乐观锁在并发控制中怎么选” 的相关文章

​手机业务被CC攻击怎么办?别只盯着带宽,这三个坑九成人会踩

好的,收到。我是那个长期写网络安全内容的作者。咱们不聊虚的,直接开干。 --- ### **第一步:分析关键词“cc攻击手机”的搜索意图** 用户搜这个词,大概率不是想了解一个学术概念。我猜他们正面临以下几种情况之一: 1.  **真实受害者**:…

探讨针对SSL/TLS拒绝服务攻击的资源分级分配与限额算法

## 当SSL/TLS攻击来袭:别让握手“握死”你的服务器 (开篇先来点“人话”) 说真的,现在搞DDoS攻击的,手法是越来越“精致”了。早些年那种“傻大黑粗”的流量洪水,现在但凡有点规模的公司,上个高防IP或者高防CDN,基本都能扛一扛。但最近两年,…

深度拆解针对验证码接口的暴力破解防御算法与人机识别逻辑

# 被“刷”到崩溃的验证码,背后藏着什么秘密? 上周,一个做电商的朋友半夜给我打电话,声音都快哭了:“我们那个登录页面,验证码明明都显示成功了,后台还是被刷了几万条垃圾注册。你说这验证码到底防了个啥?” 我让他把日志发来看看。好家伙,攻击者根本就没“看…

分析高防 CDN 接入后 CSS/JS 文件未生效的缓存刷新排查指南

# 高防CDN接上,网站样式全崩了?别慌,手把手教你“救活”CSS/JS ˃ **先说个我亲眼见过的场景**:技术小哥忙活一下午,终于把高防CDN给接上了,搓着手准备迎接“刀枪不入”的新时代。结果一刷新页面——好家伙,整个网站排版稀碎,图片错位,按钮点不…

解析高防 CDN 接入后部分区域无法访问的 DNS 与路由排查方法

## 解析高防 CDN 接入后部分区域无法访问的 DNS 与路由排查方法 说真的,但凡用过所谓“高防CDN”的,十个里有八个都遇到过这种破事:防护一开,网站是安全了,可某些地区的用户死活打不开了。客服那边呢,要么让你“耐心等待”,要么甩给你一句“本地网络…

详解自建高防 CDN 的防盗链与 Referer 校验逻辑的工程实现

# 别让盗链把你家服务器“吃空”——聊聊自建高防CDN里那些防盗链的硬核操作 前两天,一个做在线教育的朋友半夜找我诉苦,说他们平台上的视频课程,莫名其妙流量暴涨,但付费用户数没动。我一听就感觉不对劲——这味儿太熟悉了。让他查了下日志,果然,大量请求的Re…