MySQL乐观锁在分布式场景下的实践
背景
在电商购物的场景下,当我们点击购物时,后端服务就会对相应的商品进行减库存操作。在单实例部署的情况,我们可以简单地使用JVM提供的锁机制对减库存操作进行加锁,防止多个用户同时点击购买后导致的库存不一致问题。
但在实践中,为了提高系统的可用性,我们一般都会进行多实例部署。而不同实例有各自的JVM,被负载均衡到不同实例上的用户请求不能通过JVM的锁机制实现互斥。
因此,为了保证在分布式场景下的数据一致性,我们一般有两种实践方式:一、使用MySQL乐观锁;二、使用分布式锁。
本文主要介绍MySQL乐观锁,关于分布式锁我在下一篇博客中介绍。
乐观锁简介
乐观锁(Optimistic Locking)与悲观锁相对应,我们在使用乐观锁时会假设数据在极大多数情况下不会形成冲突,因此只有在数据提交的时候,才会对数据是否产生冲突进行检验。如果产生数据冲突了,则返回错误信息,进行相应的处理。
那我们如何来实现乐观锁呢?一般采用以下方式:使用版本号(version)机制来实现,这是乐观锁最常用的实现方式。
版本号
那什么是版本号呢?版本号就是为数据添加一个版本标志,通常我会为数据库中的表添加一个int类型的"version"字段。当我们将数据读出时,我们会将version字段一并读出;当数据进行更新时,会对这条数据的version值加1。当我们提交数据的时候,会判断数据库中的当前版本号和第一次取数据时的版本号是否一致,如果两个版本号相等,则更新,否则就认为数据过期,返回错误信息。我们可以用下图来说明问题:
如图所示,如果更新操作如第一个图中一样顺序执行,则数据的版本号会依次递增,不会有冲突出现。但是像第二个图中一样,不同的用户操作读取到数据的同一个版本,再分别对数据进行更新操作,则用户的A的更新操作可以成功,用户B更新时,数据的版本号已经变化,所以更新失败。
代码实践
我们对某个商品减库存时,具体操作分为以下3个步骤:
查询出商品的具体信息
根据具体的减库存数量,生成相应的更新对象
修改商品的库存数量
为了使用MySQL的乐观锁,我们需要为商品表goods加一个版本号字段version,具体的表结构如下:
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
CREATE TABLE `goods` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `name` varchar(64) NOT NULL DEFAULT '',  `remaining_number` int(11) NOT NULL,  `version` int(11) NOT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; | 
 
Goods类的Java代码:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
 | 
* 商品名字     */    private String name;    /**     * 库存数量     */    private Integer remainingNumber;    /**     * 版本号     */    private Integer version;    @Override    public String toString() {        return "Goods{" +                "id=" + id +                ", name='" + name + '\'' +                ", remainingNumber=" + remainingNumber +                ", version=" + version +                '}';    }} | 
 
GoodsMapper.java:
| 
 1 
2 
3 
4 
5 
 | 
public interface GoodsMapper {    Integer updateGoodCAS(Goods good);} | 
 
GoodsMapper.xml如下:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
 | 
<update id="updateGoodCAS" parameterType="com.ztl.domain.Goods">        <![CDATA[          update goods          set `name`=#{name},          remaining_number=#{remainingNumber},          version=version+1          where id=#{id} and version=#{version}        ]]>    </update> | 
 
GoodsService.java 接口如下:
| 
 1 
2 
3 
4 
5 
 | 
public interface GoodsService {    @Transactional    Boolean updateGoodCAS(Integer id, Integer decreaseNum);} | 
 
GoodsServiceImpl.java类如下:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
 | 
@Servicepublic class GoodsServiceImpl implements GoodsService {    @Autowired    private GoodsMapper goodsMapper;    @Override    public Boolean updateGoodCAS(Integer id, Integer decreaseNum) {        Goods good = goodsMapper.selectGoodById(id);        System.out.println(good);        try {            Thread.sleep(3000);     //模拟并发情况,不同的用户读取到同一个数据版本        } catch (InterruptedException e) {            e.printStackTrace();        }        good.setRemainingNumber(good.getRemainingNumber() - decreaseNum);        int result = goodsMapper.updateGoodCAS(good);        System.out.println(result == 1 ? "success" : "fail");        return result == 1;    }} | 
 
GoodsServiceImplTest.java测试类
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
 | 
@RunWith(SpringRunner.class)@SpringBootTestpublic class GoodsServiceImplTest {    @Autowired    private GoodsService goodsService;    @Test    public void updateGoodCASTest() {        final Integer id = 1;        Thread thread = new Thread(new Runnable() {            @Override            public void run() {                goodsService.updateGoodCAS(id, 1);    //用户1的请求            }        });        thread.start();        goodsService.updateGoodCAS(id, 2);            //用户2的请求        System.out.println(goodsService.selectGoodById(id));    }} | 
 
输出结果:
| 
 1 
2 
3 
4 
5 
 | 
Goods{id=1, name='手机', remainingNumber=10, version=9}Goods{id=1, name='手机', remainingNumber=10, version=9}successfailGoods{id=1, name='手机', remainingNumber=8, version=10} | 
 
代码说明:
在updateGoodCASTest()的测试方法中,用户1和用户2同时查出id=1的商品的同一个版本信息,然后分别对商品进行库存减1和减2的操作。从输出的结果可以看出用户2的减库存操作成功了,商品库存成功减去2;而用户1提交减库存操作时,数据版本号已经改变,所以数据变更失败。
这样,我们就可以通过MySQL的乐观锁机制保证在分布式场景下的数据一致性。
以上。
MySQL乐观锁在分布式场景下的实践的更多相关文章
- 【转】MySQL乐观锁在分布式场景下的实践
		
背景 在电商购物的场景下,当我们点击购物时,后端服务就会对相应的商品进行减库存操作.在单实例部署的情况,我们可以简单地使用JVM提供的锁机制对减库存操作进行加锁,防止多个用户同时点击购买后导致的库存不 ...
 - 浅谈Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景
		
浅谈Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景 Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景 一.相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁 ...
 - MySQL 乐观锁与悲观锁
		
悲观锁 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁. 悲观锁: ...
 - mysql乐观锁总结和实践--转
		
原文地址:http://chenzhou123520.iteye.com/blog/1863407 上一篇文章<MySQL悲观锁总结和实践>谈到了MySQL悲观锁,但是悲观锁并不是适用于任 ...
 - 使用mysql乐观锁解决并发问题
		
案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交.最后实际账户余额为1000 ...
 - mysql乐观锁总结和实践(转)
		
原文:mysql乐观锁总结和实践 上一篇文章<MySQL悲观锁总结和实践>谈到了MySQL悲观锁,但是悲观锁并不是适用于任何场景,它也有它存在的一些不足,因为悲观锁大多数情况下依靠数据库的 ...
 - 使用mysql乐观锁解决并发问题思路
		
本文摘自网络,仅供个人学习之用 案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后 ...
 - 面试官:如何在分布式场景下生成全局唯一 ID?
		
在分布式系统中,有一些场景需要使用全局唯一 ID ,可以和业务场景有关,比如支付流水号,也可以和业务场景无关,比如分库分表后需要有一个全局唯一 ID,或者用作事务版本号.分布式链路追踪等等,好的全局唯 ...
 - 分布式场景下Kafka消息顺序性的思考
		
如果业务中,对于kafka发送消息异步消费的场景,在业务上需要实现在消费时实现顺序消费, 利用kafka在partition内消息有序的特点,消息消费时的有序性. 1.在发送消息时,通过指定parti ...
 
随机推荐
- Mysql中的锁机制
			
原文:http://blog.csdn.net/soonfly/article/details/70238902 锁是计算机协调多个进程或线程并发访问某一资源的机制.在数据库中,除传统的 计算资源(如 ...
 - 20145236《网络对抗》Exp2 后门原理与实践
			
20145236<网络对抗>Exp2 后门原理与实践 目录: 一.基础问题回答 二.常用后门工具实践 2.1 Windows获得Linux Shell 2.2 Linux获得Windows ...
 - linux如何查看端口被哪个进程占用
			
1.lsof -i:端口号 2.netstat -tunlp|grep 端口号 都可以查看指定端口被哪个进程占用的情况 工具/原料 linux,windows xshell 方法/步骤 [ ...
 - <操作系统>内存管理
			
单道程序设计:内存被划分为两部分:一部分供操作系统使用,另一部分供当前正在执行的程序使用. 多道程序设计:内存中进一步细分用户部分,以满足多个进程的要求. 内存管理的需求: 重定位:程序从磁盘换入内存 ...
 - WebSockets通信
			
WebSockets通信 1. websocket是什么?WebSocket是一种网络通信协议.2. 为什么需要websocket?我们有http协议,为什么还需要websocket协议呢?因为htt ...
 - Linux 局域网同步时间
			
选择一台能上外网的机器作为时间服务器(都不能上亦可以,任选一台即可,但是只能保证局域网内时间同步) 配置此时间服务器 安装 ntp 在 /etc/ntp.conf 中配置 restrict 127.0 ...
 - 前端知识点总结(html+css)部分
			
HTML 1.一套规则,浏览器认识的规则. 2.开发者: 学习Html规则 开发后台程序: - 写Html文件(充当模板的作用) ****** - 数据库获取数据,然后替换到html文件的指定位置(W ...
 - HTML5 读取上传文件(转载)
			
另参考 http://www.jianshu.com/p/46e6e03a0d53 1 filelist对象与file对象: <input type="file" id=&q ...
 - python 3.5下安装pycrypto
			
pip install --use-wheel --no-index --find-links=https://github.com/sfbahr/PyCrypto-Wheels/raw/master ...
 - c#对联合体的封装
			
https://blog.csdn.net/u012846041/article/details/37518313 标准C或者C++中均提供关键字定义联合结构,C#中未提供类似的关键字,但仍然可以定义 ...