MySQL 乐观锁原理、实现、踩坑与实战完整指南

 更新时间:2026年08月20日 09:51:48   作者:夜雪一千  
乐观锁是用‌极短的更新锁‌替代‌长事务阻塞锁‌,核心在于‌UPDATE 语句的 WHERE 条件校验‌与‌应用层重试机制‌的配合;生产环境首选‌整型版本号‌,严禁将校验逻辑放在数据库之外,‌‌本文介绍MySQL乐观锁原理、实现、踩坑与实战完整指南,感兴趣的朋友一起看看吧

前言

在开发扣库存、账户余额变更、订单状态流转这类并发业务时,如果不加控制,高并发下很容易出现超卖、数据覆盖、余额错乱问题。

很多同学第一反应想到用 MySQL 悲观锁 select ... for update,但是悲观锁会加行锁,并发高时容易出现锁等待、死锁、性能下降。
这时就有了另一种方案:乐观锁。

很多人听过乐观锁,但是搞不清楚:

  • 乐观锁到底是数据库自带功能吗?
  • 版本号怎么写SQL?
  • 时间戳做乐观锁有什么坑?
  • 和悲观锁、事务隔离级别是什么关系?

本文结合业务实战讲透 MySQL 乐观锁。

说明:乐观锁并不是 MySQL 内置的锁机制,它是一种业务层实现的并发控制思想,数据库本身没有 lock optimistic 这类语法。

一、乐观锁与悲观锁核心思想对比

悲观锁

悲观的认为:并发一定会发生冲突,查询数据的时候就直接上锁,其他人必须等待锁释放才能操作。

  • 实现:SELECT ... FOR UPDATE,行排他锁
  • 特点:先加锁,后修改;冲突时阻塞等待;
  • 缺点:高并发场景锁竞争激烈,容易超时、死锁,吞吐量低。

乐观锁

乐观的认为:大部分时候不会发生并发冲突,不上锁,直接执行业务修改;提交更新的时候再检查,看看数据有没有被别人修改过。

  • 实现:业务版本号 / 时间戳,基于 update where 条件做版本校验
  • 特点:不加锁,读无阻塞;冲突发生时直接返回更新行数0,业务捕获后做重试或者报错;
  • 缺点:需要业务代码处理冲突重试;不能解决脏读问题。
类型锁机制并发冲突处理性能典型实现
悲观锁数据库行锁阻塞等待并发高较差select … for update
乐观锁业务逻辑,无数据库锁更新判断,失败抛异常/重试并发高较好version版本号

二、乐观锁两种主流实现方案

方案1:版本号 version(推荐,生产首选)

核心原理:给业务表增加一个 version 版本字段,数字类型。

  1. 查询时,把当前 version 一并读出来;
  2. 更新的时候,where 条件带上 version = 查询出来的旧版本号;
  3. 更新成功同时 version = version + 1;
  4. 如果这条SQL影响行数为0,代表期间已经被其他线程修改过,发生冲突。

建表示例(库存表)

CREATE TABLE `goods_stock` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `stock` int NOT NULL DEFAULT '0' COMMENT '库存',
  `version` int NOT NULL DEFAULT '0' COMMENT '乐观锁版本号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

业务执行流程

  1. 查询库存与版本号
SELECT id, stock, version FROM goods_stock WHERE id = 1;
-- 返回 stock=10,version=1
  1. 业务判断库存充足,执行扣减,更新条件带上旧version
UPDATE goods_stock 
SET stock = stock -1, version = version +1 
WHERE id = 1 AND version = 1;

关键点:

  • 如果更新返回 affected_rows = 1:更新成功;
  • 如果更新返回 affected_rows = 0:说明在查询和更新间隙,有其他请求已经修改过这条数据,version已经改变,本次操作冲突失败。

Python伪代码示例

# 1 查询
row = db.query("SELECT id,stock,version FROM goods_stock WHERE id=1")
stock = row["stock"]
old_version = row["version"]
if stock <= 0:
    print("库存不足")
# 2 扣库存更新
affect_rows = db.execute(
    "UPDATE goods_stock SET stock=stock-1,version=version+1 WHERE id=%s AND version=%s",
    (1, old_version)
)
if affect_rows == 0:
    # 乐观锁冲突,需要重试或者返回提示用户
    raise Exception("并发冲突,请重试")

方案2:使用更新时间戳update_time实现(不推荐优先使用)

利用数据的修改时间作为校验依据,不需要额外增加version字段。
表字段:update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

执行逻辑:

-- 查询拿到旧的更新时间
SELECT id,stock,update_time FROM goods_stock WHERE id=1;
-- 更新带上旧时间戳
UPDATE goods_stock 
SET stock = stock -1 
WHERE id =1 AND update_time = '2026-08-19 10:00:00';

时间戳方案重大缺陷:

  1. datetime 只到秒级别,如果同一秒内多次修改,时间戳不变,乐观锁失效,出现数据覆盖;
  2. 如果业务代码手动修改了update_time字段,会直接破坏乐观锁逻辑。

✅建议:优先使用数字version版本号。

三、乐观锁常见坑点

坑1:只在代码做版本判断,没有放到update where条件中

❌错误写法

# 先查询version,代码里面if判断
row = query(...)
if row.version == old_version:
    execute("update goods_stock set stock=stock-1 where id=1")

查询和update是两条SQL,中间存在时间窗口,并发下会出现竞态条件,乐观锁完全失效。
版本校验必须写在update的where条件里面,交给数据库原子执行。

坑2:乐观锁不能解决ABA问题

ABA场景:
线程A读取version=1;
线程B修改数据version变为2,又改回version=1;
线程A执行更新,where version=1条件命中,更新成功。
业务数据实际发生过变更,但是版本号回到原值,更新直接放行。

  • version自增方案:天然规避ABA,每次修改version必然+1,不会回到旧值;
  • 时间戳方案:会存在ABA风险。

大部分互联网业务场景ABA并不多见,version自增可以解决绝大多数场景。

坑3:乐观锁只针对行级别,无法处理批量更新

如果需要批量更新多条记录,乐观锁每一行都要携带version条件;
批量并发很高的场景,大量更新返回0,大量冲突重试,CPU压力上升。

坑4:乐观锁不能替代事务

乐观锁依然需要事务保证业务一致性。比如扣库存同时生成订单,需要事务包裹保证多条SQL要么全部成功,要么全部回滚。
乐观锁解决并发覆盖问题,不解决原子性问题。

坑5:高并发下大量冲突,疯狂重试

秒杀场景大量请求同时竞争同一条库存记录,乐观锁大量返回影响行数0,业务不断重试,会把数据库压垮。

优化:上层做限流、队列削峰,不要单纯依靠乐观锁硬抗大并发。

四、乐观锁 vs 悲观锁 该怎么选

使用乐观锁场景

  1. 读多写少,冲突概率不高;
  2. 追求高吞吐量,不想出现锁等待;
  3. 冲突发生允许业务重试,比如扣库存、用户信息修改。

使用悲观锁场景

  1. 写冲突概率非常高,乐观锁大量冲突重试代价更大;
  2. 业务不能接受失败重试,必须保证一定拿到最新数据;
  3. 锁持有时间很短。

举例子:
商品库存秒杀:读多写少,优先乐观锁;
金融账户转账,强一致性,冲突概率高,可考虑悲观锁。

五、MyBatis-Plus 中乐观锁简单了解(拓展)

很多Java开发使用MP内置乐观锁插件,原理和我们手写SQL完全一样:

  1. 实体增加 @Version 注解标记version字段;
  2. 开启乐观锁插件;
  3. 更新实体对象,MP自动拼接 WHERE version = ? 并且自动 version+1。

底层依然只是封装SQL,数据库本身没有特殊锁。

六、完整业务流程模板

1.开启事务
2.查询业务数据,同时读取version版本号
3.业务逻辑校验(库存是否大于0等)
4.执行update set xxx=xxx,version=version+1 where id=? and version=?
5.判断update返回影响行数
    - 等于1:commit提交事务,业务成功
    - 等于0:rollback回滚,发生并发冲突,抛出异常或重试

重试小提示:重试不要无限循环,设置最大重试次数3~5次,防止死循环。

总结

  1. 乐观锁不是MySQL数据库自带锁,是业务层并发控制思想,依靠version版本号+update where条件实现;
  2. 数字自增version是最优方案;时间戳datetime存在秒级冲突漏洞,谨慎使用;
  3. 版本校验必须写在update的where条件,不能在代码内存中判断;
  4. version自增可以规避ABA问题;
  5. 乐观锁适合读多写少场景,冲突时返回影响行数0,业务需要处理冲突与重试;
  6. 乐观锁不能替代事务;高并发热点数据需要配合限流、消息队列,避免大量重试压垮数据库;
  7. 和悲观锁区别:乐观锁不上锁,冲突检测;悲观锁先上锁,阻塞等待。

记住一句核心:乐观锁本质就是利用数据库 update 的原子性,把「读取-判断-修改」的判断逻辑放到数据库单条SQL内部执行。

到此这篇关于MySQL 乐观锁原理、实现、踩坑与实战完整指南的文章就介绍到这了,更多相关mysql乐观锁原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MySQL主键自增会遇到的坑及解决方法

    MySQL主键自增会遇到的坑及解决方法

    主键不用随机字符串用什么?主键自增?主键自增就是最佳方案吗?有没有其他坑?今天我们就来讨论下这个话题,感兴趣的小伙伴可以收藏一下
    2023-04-04
  • Mysql 模糊查询和正则表达式实例详解

    Mysql 模糊查询和正则表达式实例详解

    在MySQL中,可以使用LIKE运算符进行模糊查询,LIKE运算符用于匹配字符串模式,其中可以使用通配符来表示任意字符或字符序列,这篇文章主要介绍了Mysql 模糊查询和正则表达式实例详解,需要的朋友可以参考下
    2023-11-11
  • mysql5.5 master-slave(Replication)主从配置

    mysql5.5 master-slave(Replication)主从配置

    在主机master中对test数据库进行sql操作,再查看从机test数据库是否产生同步。
    2011-07-07
  • MySQL数据类型与表操作全指南( 从基础到高级实践)

    MySQL数据类型与表操作全指南( 从基础到高级实践)

    本文详解MySQL数据类型分类(数值、日期/时间、字符串)及表操作(创建、修改、维护),涵盖优化技巧如数据类型选择、备份、分区,强调规范设计与实际应用结合,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧
    2025-08-08
  • mysql5.7.15winx64配置全过程

    mysql5.7.15winx64配置全过程

    文章详细介绍了MySQL 5.7.15免安装版的配置步骤,包括解压安装包、设置环境变量、修改配置文件、初始化数据目录、安装服务、启动数据库、登录及密码修改、配置远程访问权限和安全策略,最后创建特定数据库和用户
    2025-08-08
  • MySQL全局共享内存介绍

    MySQL全局共享内存介绍

    这篇文章主要介绍了MySQL全局共享内存介绍,全局共享内存则主要是 MySQL Instance(mysqld进程)以及底层存储引擎用来暂存各种全局运算及可共享的暂存信息,如存储查询缓存的 Query Cache,缓存连接线程的 Thread Cache等等,需要的朋友可以参考下
    2014-12-12
  • Mybatis报错: org.apache.ibatis.exceptions.PersistenceException解决办法

    Mybatis报错: org.apache.ibatis.exceptions.PersistenceException

    这篇文章主要介绍了Mybatis报错: org.apache.ibatis.exceptions.PersistenceException解决办法的相关资料,需要的朋友可以参考下
    2016-12-12
  • 浅谈MySQL数据库的备份与恢复

    浅谈MySQL数据库的备份与恢复

    MYSQL数据库的备份、恢复等是每一位信息管理人员应必备的能力,因此掌握MYSQL数据库管理的技巧会使您的工作事半功倍,这里我们来简单总结下。
    2017-01-01
  • Windows Server 2003 下配置 MySQL 集群(Cluster)教程

    Windows Server 2003 下配置 MySQL 集群(Cluster)教程

    这篇文章主要介绍了Windows Server 2003 下配置 MySQL 集群(Cluster)教程,本文先是讲解了原理知识,然后给出详细配置步骤和操作方法,需要的朋友可以参考下
    2015-06-06
  • 详解DBeaver连接MySQL8以上版本以及解决可能遇到的问题

    详解DBeaver连接MySQL8以上版本以及解决可能遇到的问题

    这篇文章主要介绍了DBeaver连接MySQL8以上版本以及解决可能遇到的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11

最新评论