千家信息网

MySQL悲观锁与乐观锁如何实现

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,这篇文章主要为大家展示了"MySQL悲观锁与乐观锁如何实现",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"MySQL悲观锁与乐观锁如何实现"这篇文章吧。前言
千家信息网最后更新 2026年11月02日MySQL悲观锁与乐观锁如何实现

这篇文章主要为大家展示了"MySQL悲观锁与乐观锁如何实现",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"MySQL悲观锁与乐观锁如何实现"这篇文章吧。

前言

悲观锁和乐观锁是用来解决并发问题的两种思想,在不同的平台有着各自的实现。例如在Java中,synchronized就可以认为是悲观锁的实现(不严谨,有锁升级的过程,升级到重量级锁才算),Atomic***原子类可以认为是乐观锁的实现。

悲观锁

具有强烈的独占和排他特性,在整个处理过程中将数据处于锁定状态,一般是通过系统的互斥量来实现。当其他线程想要获取锁时会被阻塞,直到持有锁的线程释放锁。

乐观锁

对数据的修改和访问持乐观态度,假设不会发生冲突,只有当数据提交更新时才会对数据冲突与否进行检测,如果没有冲突则顺利提交更新,否则快速失败,返回一个错误给用户,让用户选择接下来该如何去做,一般来说失败后会继续重试,直到提交更新成功为止。

MySQL本身就支持锁机制,例如我们有一个「先查再写」的需求,我们希望整个流程是一个原子操作,中间不能被打断,这时候就可以通过给查询的数据行加「排他锁」来实现。只要当前事务不释放锁,其他事务要想获得排他锁,MySQL就会将其阻塞,直到当前事务释放锁。这种MySQL底层的排他锁就称作「悲观锁」。

MySQL本身不提供乐观锁的功能,需要开发者自己实现。普遍的做法是在表中加一个version列,用来标记数据行的版本,当我们需要更新数据时,必须比对version版本,version一致说明这个期间数据没有被其他事务修改过,否则说明数据已经被其他事务修改,需要自旋重试了。

实战

假设数据库有两张表:商品表和订单表。

用户下单后需要执行两个操作:

  1. 商品表减去库存。

  2. 订单表创建一条记录。

初始数据:ID为1的商品有100的库存,订单表数据为空。

客户端启动10个线程并发下单,分别在无锁、悲观锁、乐观锁的场景下有哪些表现。

如下是创建表的sql语句:

-- 商品表CREATE TABLE `goods` (  `id` bigint(20) NOT NULL AUTO_INCREMENT,  `goods_name` varchar(50) NOT NULL,  `price` decimal(10,2) NOT NULL,  `stock` int(11) DEFAULT '0',  `version` int(10) unsigned NOT NULL DEFAULT '0',  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8-- 订单表CREATE TABLE `t_order` (  `id` bigint(20) NOT NULL AUTO_INCREMENT,  `goods_id` bigint(20) NOT NULL,  `order_time` datetime NOT NULL,  PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

1、无锁

不做任何处理。

// 下单private boolean order(){    Goods goods = goodsMapper.selectById(1L);    boolean success = false;    if (goods.getStock() > 0) {        goods.setStock(goods.getStock() - 1);        // 更新库存        goodsMapper.updateById(goods);        // 创建订单        orderMapper.save(goods.getId());        success = true;    }    return success;}

控制台输出结果:

2、悲观锁

查询商品时加FOR UPDATE,给数据行加排他锁,这样其他线程再查询时就会被阻塞,直到当前线程的事务提交并释放锁,其他线程才能继续下单。这种方式并发性能不高。

sql语句

@Select("SELECT * FROM goods WHERE id = #{id} FOR UPDATE")Goods selectForUpdate(Long id);

控制台输出结果:

注意:FOR UPDATE必须在事务中才有效,查询和更新必须在同一个事务中!!!

3、乐观锁

实现思路是:每次更新时校验版本号,如果版本号一致说明期间数据没有被其他线程改过,当前线程可以正常提交更新,否则说明数据已经被其他线程改过了,当前线程需要自旋重试,直到业务成功为止。

更新数据的同时版本号必须自增!!!

@Update("UPDATE goods SET stock = #{stock},version = version+1 WHERE id = #{id} AND version = #{version}")int updateByVersion(Long id, Integer stock, Integer version);

业务代码

boolean order(){    Goods goods = goodsMapper.selectById(1L);    boolean success = false;    if (goods.getStock() > 0) {        goods.setStock(goods.getStock() - 1);        // 更新库存,带上版本号        int result = goodsMapper.updateByVersion(goods.getId(), goods.getStock(), goods.getVersion());        if (result <= 0) {            // 更新失败,说明期间数据已经被其他线程修改,需要递归重试            return order();        }        // 创建订单        orderMapper.save(goods.getId());        success = true;    }    return success;}

控制台输出结果:

以上是"MySQL悲观锁与乐观锁如何实现"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

数据 线程 乐观 更新 悲观 事务 版本 订单 商品 库存 查询 内容 控制台 用户 篇文章 结果 冲突 控制 输出 阻塞 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 喔噻互联网科技有限公司 云之行互联网科技 苏州找大状互联网科技有限公司 互联网科技财经 互联网科技会 美国互联网科技巨头监管 互联网科技资迅 电子科技大学互联网 互联网科技logo 广州善班互联网科技 四川狮子座互联网科技 栖莱科技互联网股份有限公司 西安小鹰速贷互联网科技有限公司 互联网科技资金流入 浙南科技城互联网大厦招聘 中国新注册互联网科技 杭州融税互联网科技有限公司 科技互联网专业工作会议发言稿 银川新华互联网科技学校招聘 伍佰万互联网科技是干嘛的 互联网科技公司的优势 互联网科技大会 互联网金融科技委员会 成都玖崟互联网科技有限公司乐商圈怎么样 科技与互联网的利与弊 互联网教育科技公司vipkid 受益于互联网科技的高速发展 本届互联网博览会上的黑科技 富国互联网金融科技混合基金 扫呗互联网科技有限公司
0