MySQL悲观锁与乐观锁的实现方案


Posted in MySQL onNovember 02, 2021

前言

悲观锁和乐观锁是用来解决并发问题的两种思想,在不同的平台有着各自的实现。例如在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;
}

控制台输出结果:

MySQL悲观锁与乐观锁的实现方案

2、悲观锁

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

sql语句

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

控制台输出结果:

MySQL悲观锁与乐观锁的实现方案

注意: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悲观锁与乐观锁的实现方案

总结

到此这篇关于MySQL悲观锁与乐观锁方案的文章就介绍到这了,更多相关MySQL悲观锁与乐观锁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
MySQL学习总结-基础架构概述
Apr 05 MySQL
MySQL令人咋舌的隐式转换
Apr 05 MySQL
MySQL Innodb关键特性之插入缓冲(insert buffer)
Apr 08 MySQL
MySQL root密码的重置方法
Apr 21 MySQL
MySQL 可扩展设计的基本原则
May 14 MySQL
详解MySQL连接挂死的原因
May 18 MySQL
MySQL 百万级数据的4种查询优化方式
Jun 07 MySQL
记一次Mysql不走日期字段索引的原因小结
Oct 24 MySQL
MySQL数据库⾼可⽤HA实现小结
Jan 22 MySQL
MySQL学习之基础命令实操总结
Mar 19 MySQL
mysql序号rownum行号实现方式
Dec 24 MySQL
MySQL深分页问题解决思路
Dec 24 MySQL
SpringBoot连接MySQL获取数据写后端接口的操作方法
Mysql排序的特性详情
Nov 01 #MySQL
MySQL分区表实现按月份归类
Nov 01 #MySQL
MySQL数据库10秒内插入百万条数据的实现
MySQL面试题讲解之如何设置Hash索引
MySQL对数据表已有表进行分区表的实现
Nov 01 #MySQL
mysql分表之后如何平滑上线详解
Nov 01 #MySQL
You might like
10条PHP高级技巧[修正版]
2011/08/02 PHP
深入PHP autoload机制的详解
2013/06/09 PHP
JavaScript实现数字数组按照倒序排列的方法
2015/04/06 Javascript
js实现仿阿里巴巴城市选择框效果实例
2015/06/24 Javascript
JavaScript实现时钟滴答声效果
2017/01/29 Javascript
手淘flexible.js框架使用和源代码讲解小结
2018/10/15 Javascript
koa2实现登录注册功能的示例代码
2018/12/03 Javascript
layui实现鼠标移动到单元格上显示数据的方法
2019/09/11 Javascript
JavaScript装箱及拆箱boxing及unBoxing用法解析
2020/06/15 Javascript
解决vue-router 嵌套路由没反应的问题
2020/09/22 Javascript
[15:28]DOTA2 HEROS教学视频教你分分钟做大人-剧毒术士
2014/06/13 DOTA
[05:29]2014DOTA2国际邀请赛 赛后专访:LGDNewbee顺利过关
2014/07/13 DOTA
python网络编程之TCP通信实例和socketserver框架使用例子
2014/04/25 Python
用于统计项目中代码总行数的Python脚本分享
2015/04/21 Python
在Python中使用cookielib和urllib2配合PyQuery抓取网页信息
2015/04/25 Python
Python pickle模块用法实例分析
2015/05/27 Python
python实现矩阵乘法的方法
2015/06/28 Python
Python+OpenCV人脸检测原理及示例详解
2020/10/19 Python
微信跳一跳python辅助软件思路及图像识别源码解析
2018/01/04 Python
Python中实现变量赋值传递时的引用和拷贝方法
2018/04/29 Python
python基于物品协同过滤算法实现代码
2018/05/31 Python
django2.2安装错误最全的解决方案(小结)
2019/09/24 Python
python读取raw binary图片并提取统计信息的实例
2020/01/09 Python
python中sort sorted reverse reversed函数的区别说明
2020/05/11 Python
瑞典轮胎在线:Tirendo.se
2018/06/21 全球购物
Ibood荷兰:互联网每日最佳在线优惠
2019/02/28 全球购物
俄罗斯玩具、儿童用品、儿童服装和鞋子网上商店:MyToys.ru
2019/10/14 全球购物
.net软件工程师面试题
2015/03/31 面试题
关于母亲节的感言
2014/02/04 职场文书
星级党支部申报材料
2014/05/31 职场文书
公民代理授权委托书
2014/09/24 职场文书
县人大领导班子四风对照检查材料思想汇报
2014/10/09 职场文书
学习三严三实心得体会
2014/10/13 职场文书
vue使用wavesurfer.js解决音频可视化播放问题
2022/04/04 Vue.js
Java的Object类的九种方法
2022/04/13 Java/Android
Win11 vmware不兼容怎么办?Win11与VMware虚拟机不兼容的解决方法
2023/01/09 数码科技