数据库乐观锁和悲观锁有什么区别?version 字段怎么防止覆盖更新?
下面是一段教学用的模拟面试。
🧑💻 面试官:乐观锁和悲观锁怎么选?
🙋♂️ 我:冲突少用乐观锁,冲突多用悲观锁。
🧑💻 面试官:两个编辑者都读到 version=7,各自提交。乐观锁在哪一步发现冲突?
🙋♂️ 我:UPDATE 时带上旧版本条件,只有一个能匹配成功。
🧑💻 面试官:那“乐观锁完全不加锁”对吗?如果第二个失败了,自动用新版本重试,就一定符合编辑者的意愿吗?
两种方法都在保护同一件事:「我准备修改时,原来的判断前提还是否成立」。
面试速答(60 秒版)
悲观方案通常在事务中先锁住相关记录,再基于受保护的数据判断和更新,其他冲突操作需要等待或按配置失败。
乐观方案先读取,写入时再验证数据是否仍符合旧前提。例如读取 version=7,提交时使用 WHERE id=? AND version=7,更新内容的同时把版本推进到 8。若影响行数为零,就不能把这次提交当成成功。
区别在于竞争控制的方式,不是有没有使用数据库锁。InnoDB 执行乐观方案的 UPDATE 时,仍然会使用必要的锁与并发控制。
选型还要看冲突成本、锁持有时间、是否需要用户确认,以及约束跨多少行。冲突少、工作可重新计算时可以考虑乐观验证;复杂短事务需要保护读取后的判断时,可以考虑悲观锁,但两者都要有明确的超时和失败处理。

知识点详解:两个编辑者,怎样避免后提交的人覆盖前一个?
普通读改写,遗漏了什么检查?
假设资料当前名称是“旧名称”,版本是 7。编辑者 A、B 都读到这份数据。
A 提交“名称 A”,B 稍后提交“名称 B”。如果两次更新只按 id 定位,B 就可能覆盖 A 的修改,却不知道自己编辑的前提已经过期。
数据库能把单次 UPDATE 正确执行,并不意味着它自动理解“B 只愿意修改他看到的那一版”。这个业务条件要写进更新规则。
乐观方案:把旧版本变成写入条件
下面 SQL 假设表名、字段、权限已经按项目确定,参数由驱动绑定:
UPDATE profile_demo
SET display_name = ?, version = version + 1
WHERE id = ? AND version = ?;
A、B 都拿旧版本 7 提交。A 先成功时,版本变成 8;B 的条件仍是 7,因此不能再匹配同一行。
应用必须检查影响行数。零行说明这次更新没有完成,可能是版本冲突,也可能是记录不存在,具体返回什么错误需要按业务进一步区分。
这里版本检查与写入属于同一条数据库条件更新,不能先 SELECT 比较版本,放开控制以后再无条件 UPDATE。后者仍然留下时间窗口。

为什么乐观更新也可能等待锁?
“乐观”通常表示应用没有先拿一把锁,覆盖整个用户编辑过程;并不是数据库写入时放弃保护。
InnoDB 的 UPDATE 仍需对相关记录进行锁定和竞争处理,两个更新可能先后执行或等待。InnoDB 语句锁文档说明了 UPDATE 等语句的锁行为。
所以,版本检查减少的是某些长时间的先锁后处理,并不能保证 UPDATE 永不等待、没有死锁,或者不受索引和隔离级别影响。
另外,参与这份记录修改的所有路径,都应遵守版本约定。如果后台任务绕过版本推进,前台就可能无法识别相关变化。
悲观方案:先保护读取,再做短时间的判断和更新
如果是在服务端执行短事务,可以先开始事务,再用 SELECT … FOR UPDATE 读取需要保护的记录,检查当前条件,然后更新、提交或回滚。
这不是让普通 SELECT 变得“更相信自己”,而是让相应冲突修改在这个事务范围内受到控制。MySQL 锁定读文档说明了锁定读与事务结束释放的关系。
不能先在自动提交的独立语句中查一下,再认为后面的业务处理一直被锁保护。也不应拿着数据库锁等用户十分钟填完表单,或者在事务里等待很慢的外部接口。
锁住哪些记录与范围,还受查询、索引和隔离级别影响。这里理解策略即可,具体行锁和间隙锁另见旧题。

冲突以后,重试还是让用户确认?
如果操作是可以重新计算的计数更新,读取最新值后重新执行可能合理,但应有限次重试,避免一直消耗资源。
如果操作是编辑者明确修改他看到的那份内容,就不能简单把版本条件替换成新版本,然后悄悄覆盖。正确做法可能是提示已经有人修改,展示差异,再让编辑者确认。
所以,冲突处理不是一个统一 catch 块。必须回答:这个操作对最新数据重新执行,是否仍表达同一个意图?
多行约束,不能只给每行加一个 version 就结束
假设业务要求两个相关记录共同满足一条约束。
分别给两行加版本,仍要定义整体检查和写入的原子边界。可能需要事务锁住相关集合、验证所有前提后统一提交,或者重新设计约束表达方式。
同样,乐观版本和 MVCC 不是同一个概念。MVCC 是数据库版本可见性的机制;应用 version 条件是特定业务并发检查。两者可以一起存在,不能拿一个词代替另一个。
SQL 已经表达本题核心,不再用 TS、Python 驱动样板代码重复它。本篇示例应在独立测试库验证,不对已有用户数据库执行更新。
面试官继续追问
可以不用 version,直接比较原字段吗?
有时可以使用条件更新表达原前提,但要明确空值、精度和需要比较的全部字段。版本字段方便表示一组受共同约束的修改,不是唯一办法。
version 用时间戳行不行?
要考虑时间精度、同一时间发生多次更新及推进规则。能可靠识别相关变化才行,不因为字段叫更新时间就自动等价于版本。
冲突很多,就必须改悲观锁吗?
先看是否存在热点和不必要的竞争,能否用一次条件 UPDATE 完成操作。悲观锁也会等待;比较整体吞吐、尾延迟和正确性后再选。
面试速记卡
- 悲观方案:事务中先保护相关读取,再判断和更新。
- 乐观方案:写入时验证旧前提,成功时同步推进版本。
- 结果检查:零行不能当成功,冲突和不存在按业务区分。
- 锁边界:乐观 UPDATE 仍有数据库锁,不等于无锁。
- 失败处理:能否重试取决于意图,多行约束需要整体边界。
公司面试真题
真题根据求职者公开面经整理,题意经过概括,非逐字原话或公司官方题库;本文为 Sunday 的独立解析。
腾讯 · 后台开发 · 原帖未明确批次
乐观锁与悲观锁有什么区别?(题意整理)
腾讯后台开发面经(HR 部门) ↗
原帖编辑于 2025-02-19