【转】MySQL乐观锁在分布式场景下的实践

背景

在电商购物的场景下,当我们点击购物时,后端服务就会对相应的商品进行减库存操作。在单实例部署的情况,我们可以简单地使用JVM提供的锁机制对减库存操作进行加锁,防止多个用户同时点击购买后导致的库存不一致问题。

但在实践中,为了提高系统的可用性,我们一般都会进行多实例部署。而不同实例有各自的JVM,被负载均衡到不同实例上的用户请求不能通过JVM的锁机制实现互斥。

因此,为了保证在分布式场景下的数据一致性,我们一般有两种实践方式:一、使用MySQL乐观锁;二、使用分布式锁。

本文主要介绍MySQL乐观锁,关于分布式锁我在下一篇博客中介绍。

乐观锁简介

乐观锁(Optimistic Locking)与悲观锁相对应,我们在使用乐观锁时会假设数据在极大多数情况下不会形成冲突,因此只有在数据提交的时候,才会对数据是否产生冲突进行检验。如果产生数据冲突了,则返回错误信息,进行相应的处理。

那我们如何来实现乐观锁呢?一般采用以下方式:使用版本号(version)机制来实现,这是乐观锁最常用的实现方式。

版本号

那什么是版本号呢?版本号就是为数据添加一个版本标志,通常我会为数据库中的表添加一个int类型的"version"字段。当我们将数据读出时,我们会将version字段一并读出;当数据进行更新时,会对这条数据的version值加1。当我们提交数据的时候,会判断数据库中的当前版本号和第一次取数据时的版本号是否一致,如果两个版本号相等,则更新,否则就认为数据过期,返回错误信息。我们可以用下图来说明问题:

如图所示,如果更新操作如第一个图中一样顺序执行,则数据的版本号会依次递增,不会有冲突出现。但是像第二个图中一样,不同的用户操作读取到数据的同一个版本,再分别对数据进行更新操作,则用户的A的更新操作可以成功,用户B更新时,数据的版本号已经变化,所以更新失败。

代码实践

我们对某个商品减库存时,具体操作分为以下3个步骤:

  1. 查询出商品的具体信息
  2. 根据具体的减库存数量,生成相应的更新对象
  3. 修改商品的库存数量

为了使用MySQL的乐观锁,我们需要为商品表goods加一个版本号字段version,具体的表结构如下:

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代码:

 * 商品名字
     */
    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:

public interface GoodsMapper {

    Integer updateGoodCAS(Goods good);

}
 

GoodsMapper.xml如下:

<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 接口如下:

public interface GoodsService {

    @Transactional
    Boolean updateGoodCAS(Integer id, Integer decreaseNum);
}
 

GoodsServiceImpl.java类如下:

@Service
public 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测试类

@RunWith(SpringRunner.class)
@SpringBootTest
public 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));
    }
}
 

输出结果:

Goods{id=1, name=‘手机‘, remainingNumber=10, version=9}
Goods{id=1, name=‘手机‘, remainingNumber=10, version=9}
success
fail
Goods{id=1, name=‘手机‘, remainingNumber=8, version=10}
 

代码说明:

在updateGoodCASTest()的测试方法中,用户1和用户2同时查出id=1的商品的同一个版本信息,然后分别对商品进行库存减1和减2的操作。从输出的结果可以看出用户2的减库存操作成功了,商品库存成功减去2;而用户1提交减库存操作时,数据版本号已经改变,所以数据变更失败。

这样,我们就可以通过MySQL的乐观锁机制保证在分布式场景下的数据一致性。

以上。

原文链接

https://segmentfault.com/a/11...

原文地址:https://www.cnblogs.com/zdd-java/p/8434796.html

时间: 2024-10-26 01:08:33

【转】MySQL乐观锁在分布式场景下的实践的相关文章

浅谈Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景

浅谈Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景 Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景 一.相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |--排他锁(X锁,MyISAM 叫做写锁) |--悲观锁(抽象性,不真实存在这个锁) |--乐观锁(抽象性,不真实存在这个锁) 二.InnoDB与MyISAM Mysql 在5.5之前默认使用 MyISAM 存储引擎,之后使用 InnoDB .查

mysql乐观锁总结和实践

上一篇文章<MySQL悲观锁总结和实践>谈到了MySQL悲观锁,但是悲观锁并不是适用于任何场景,它也有它存在的一些不足,因为悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性.如果加锁的时间过长,其他用户长时间无法访问,影响了程序的并发访问性,同时这样对数据库性能开销影响也很大,特别是对长事务而言,这样的开销往往无法承受.所以与悲观锁相对的,我们有了乐观锁,具体参见下面介绍: 乐观锁介绍: 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据

mysql乐观锁总结和实践(转)

原文:mysql乐观锁总结和实践 上一篇文章<MySQL悲观锁总结和实践>谈到了MySQL悲观锁,但是悲观锁并不是适用于任何场景,它也有它存在的一些不足,因为悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性.如果加锁的时间过长,其他用户长时间无法访问,影响了程序的并发访问性,同时这样对数据库性能开销影响也很大,特别是对长事务而言,这样的开销往往无法承受.所以与悲观锁相对的,我们有了乐观锁,具体参见下面介绍: 乐观锁介绍: 乐观锁( Optimistic Locking )

还没弄懂分布式场景下数据一致性问题?一文教你轻松解决!

文章纲要 此次分享的缘由 目前分布式事务问题是怎么解决的 行业中有什么解决方案 这些解决方案分别有什么优缺点 别人是怎么做的 我们可以怎么来做 此次分享的缘由 支付重构 考虑支付重构的时候,自然想到原本属于一个本地事务中的处理,现在要跨应用了要怎么处理.拿充值订单举个栗子吧,假设:原本订单模块和账户模块是放在一起的,现在需要做服务拆分,拆分成订单服务,账户服务.原本收到充值回调后,可以将修改订单状态和增加金币放在一个mysql事务中完成的,但是呢,因为服务拆分了,就面临着需要协调2个服务才能完成

使用mysql乐观锁解决并发问题

案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交.最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050.这就是典型的并发问题. 乐观锁机制在一定程度上解决了这个问题.乐观锁,大多是基于数据版本(Version)记录机制实现.何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version”

使用mysql乐观锁解决并发问题思路

本文摘自网络,仅供个人学习之用 案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交.最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050.这就是典型的并发问题. 乐观锁机制在一定程度上解决了这个问题.乐观锁,大多是基于数据版本(Version)记录机制实现.何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据

分布式场景下Kafka消息顺序性的思考

如果业务中,对于kafka发送消息异步消费的场景,在业务上需要实现在消费时实现顺序消费, 利用kafka在partition内消息有序的特点,消息消费时的有序性. 1.在发送消息时,通过指定partition hash 2.consumer 消费消息时,需要使用亲缘性线程池进行消费,才能实现消息的基本有序.否则即使通过发送时指定partition,在消费端由于线程池的异步消费,消息之间的处理都是并发进行的,消息就会被打乱. 上面的方式基本可以实现消息的消费顺序性,除了在极端场景下,比如: 1.进

mysql乐观锁总结和实践 (转)

上一篇文章<MySQL悲观锁总结和实践>谈到了MySQL悲观锁,但是悲观锁并不是适用于任何场景,它也有它存在的一些不足,因为悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性.如果加锁的时间过长,其他用户长时间无法访问,影响了程序的并发访问性,同时这样对数据库性能开销影响也很大,特别是对长事务而言,这样的开销往往无法承受.所以与悲观锁相对的,我们有了乐观锁,具体参见下面介绍: 乐观锁介绍: 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据

mysql乐观锁总结和实践(转载)

原文地址:http://chenzhou123520.iteye.com/blog/1863407 乐观锁介绍: 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做.那么我们如何实现乐观锁呢,一般来说有以下2种方式: 1.使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式.何谓数据版本?即为数