基于mysql乐观锁实现秒杀的示例代码

下面是基于MySQL乐观锁实现秒杀的完整攻略:

背景介绍

在高并发场景下,主要涉及到的两个问题是:安全性与性能。乐观锁技术可以在不加锁的情况下保证多个并发请求对同一资源进行操作时,不会发生数据覆盖的情况。

技术方案

  • 在MySQL中,通过对update语句设置where条件来实现乐观锁控制。
  • 在应用层面,可以通过重试机制来实现乐观锁。

示例说明

下面通过两个示例来详细讲解如何基于MySQL乐观锁实现秒杀。

示例一

首先创建一个名为seckill的数据表,包含以下字段:

id INT PRIMARY KEY AUTO_INCREMENT COMMENT '秒杀id',  
seckill_name VARCHAR(120) NOT NULL COMMENT '秒杀名称',  
start_time TIMESTAMP NOT NULL COMMENT '秒杀开始时间',  
end_time TIMESTAMP NOT NULL COMMENT '秒杀结束时间',  
total INT NOT NULL COMMENT '秒杀商品总数量',  
version INT NOT NULL COMMENT '秒杀版本号',  
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',  
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间'

其中,version字段表示当前秒杀数据的版本号。

接下来,编写Java代码,实现对秒杀商品进行秒杀的功能:

public class SeckillService {
  private final AtomicInteger state = new AtomicInteger(0);
  private final SeckillMapper seckillMapper;

  public SeckillService(SeckillMapper seckillMapper) {
    this.seckillMapper = seckillMapper;
  }

  /**
   * 秒杀商品
   *
   * @param seckillId 秒杀id
   * @param userPhone 用户手机号
   * @return 秒杀结果
   */
  public SeckillExecution executeSeckill(long seckillId, long userPhone) {
    Seckill seckill = seckillMapper.queryById(seckillId);

    if (seckill == null) {
      return new SeckillExecution(seckillId, SeckillStateEnum.NOT_FOUND);
    }

    long nowTime = System.currentTimeMillis();
    if (nowTime < seckill.getStartTime().getTime() || nowTime > seckill.getEndTime().getTime()) {
      return new SeckillExecution(seckillId, SeckillStateEnum.REPEATED);
    }

    // 查询库存数量,如果库存不足,则不进行秒杀
    int stockCount = seckillMapper.getStockCount(seckillId);
    if (stockCount == 0) {
      return new SeckillExecution(seckillId, SeckillStateEnum.NO_STOCK);
    }

    int updCount = 0;
    while (updCount == 0) {
      updCount = seckillMapper.reduceStock(seckillId, seckill.getVersion() + 1);
    }

    SeckillSuccess success = new SeckillSuccess();
    success.setSeckillId(seckillId);
    success.setUserPhone(userPhone);
    success.setState(SeckillStateEnum.SEC_KILLED.getState());
    success.setCreateTime(new Date());

    int insertCount = seckillMapper.insertSuccessKilled(success);
    if (insertCount == 0) {
      updCount = seckillMapper.reduceStock(seckillId, seckill.getVersion() - 1);
      return new SeckillExecution(seckillId, SeckillStateEnum.REPEATED);
    }

    SeckillSuccess querySuccess = seckillMapper
        .querySuccessKilledWithSeckill(seckillId, userPhone, success.getCreateTime());

    return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, querySuccess);
  }
}

在上述代码中,查询库存数量后,通过while循环进行乐观锁的实现,如果更新库存行数为0,则重试。

示例二

此示例中,使用Spring Boot 2.3.7版本进行开发。

首先创建以下两个表:

创建秒杀商品表,包含以下字段:

id INT PRIMARY KEY AUTO_INCREMENT COMMENT '秒杀id',  
seckill_name VARCHAR(120) NOT NULL COMMENT '秒杀名称',  
start_time TIMESTAMP NOT NULL COMMENT '秒杀开始时间',  
end_time TIMESTAMP NOT NULL COMMENT '秒杀结束时间',  
total INT NOT NULL COMMENT '秒杀商品总数量'

创建秒杀商品记录表,包含以下字段:

id INT PRIMARY KEY AUTO_INCREMENT COMMENT '秒杀记录id',  
seckill_id INT NULL COMMENT '秒杀id',  
user_phone BIGINT NULL COMMENT '用户手机号',  
state INT NULL COMMENT '秒杀状态',  
create_time TIMESTAMP NULL COMMENT '秒杀时间',
version INT NOT NULL COMMENT '乐观锁版本号'

其中,version字段表示当前秒杀记录的版本号。

接下来,编写Java代码,实现基于Spring Boot 2.3.7版本的秒杀功能:

@Service
@Slf4j
public class SeckillService {
  private final SeckillMapper seckillMapper;
  private final SeckillRecordMapper seckillRecordMapper;

  public SeckillService(SeckillMapper seckillMapper,
                        SeckillRecordMapper seckillRecordMapper) {
    this.seckillMapper = seckillMapper;
    this.seckillRecordMapper = seckillRecordMapper;
  }

  @Transactional(rollbackFor = Exception.class)
  public int seckill(long seckillId, long userPhone) {
    Seckill seckill = seckillMapper.queryById(seckillId);
    if (seckill == null) {
      throw new RuntimeException("秒杀活动不存在");
    }
    Date now = new Date();
    if (seckill.getEndTime().getTime() < now.getTime() ||
        seckill.getStartTime().getTime() > now.getTime()) {
      throw new RuntimeException("秒杀活动未开始或已结束");
    }
    int result = seckillMapper.reduceInventory(seckillId,
        seckill.getVersion(),
        new Date());
    if (result == 0) {
      throw new RuntimeException("秒杀商品已经被抢光了!");
    } else {
      int insResult = seckillRecordMapper.insertRecord(seckillId, userPhone);
      log.info("插入秒杀记录{},结果{}", seckillId, insResult);
      return result;
    }
  }
}

在上述代码中,通过@Transactional注解来实现对数据库的事务管理,从而保证秒杀过程中的数据一致性。其中,SeckillMapper类和SeckillRecordMapper类分别用于操作秒杀商品表和秒杀商品记录表。

至此,就完成了基于MySQL乐观锁实现秒杀的示例代码的完整攻略。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:基于mysql乐观锁实现秒杀的示例代码 - Python技术站

(0)
上一篇 2023年5月21日
下一篇 2023年5月21日

相关文章

  • spring boot项目application.properties文件存放及使用介绍

    介绍 application.properties是SpringBoot项目中常用的一种配置文件,可以用来定义项目的各种属性值,其中包括:数据库链接信息、各种组件的属性以及其他一些自定义属性值等等。本文将对application.properties的存放位置、使用方法以及示例进行详细的介绍。 存放位置 在一个SpringBoot项目中,applicatio…

    database 2023年5月18日
    00
  • LINUX系统下MySQL 压力测试工具super smack

    那么下面我将详细讲解“LINUX系统下MySQL 压力测试工具super smack”的完整攻略,包含安装、配置、使用和示例。 安装 在Linux下安装Super Smack,首先需要确保已经安装了MySQL客户端和Perl。可以使用以下命令进行安装: sudo apt-get install mysql-client sudo apt-get instal…

    database 2023年5月22日
    00
  • Oracle别名使用要点小结

    让我为你详细讲解一下“Oracle别名使用要点小结”的完整攻略。 什么是别名 在Oracle中,别名是指为一个或多个表、列、函数等定义一个临时名称,以便于在查询语句中更方便的使用。 别名的作用 别名有多种作用,主要包括: 缩短查询语句长度 提高查询语句的可读性 解决表名或列名过长的问题 允许对同一表使用多个别名,用于解决自连接问题等 别名使用要点 在使用别名…

    database 2023年5月21日
    00
  • Linux下PHP连接Oracle数据库

    如何在 Linux 系统下使用 PHP 连接 Oracle 数据库?本文将详细介绍完整的步骤,以及两条示例说明。 准备工作 在开始之前,我们需要准备以下工作: 安装 PHP: 要使用 PHP,首先需要在 Linux 系统上安装 PHP。可以根据自己的实际情况选择使用 apt-get、yum 或 make 等方式进行安装。 安装 Oracle Instant …

    database 2023年5月22日
    00
  • redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect time out

    redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed outat redis.clients.jedis.Connection.connect(Connection.java:154)at redis.cl…

    Redis 2023年4月16日
    00
  • MySQL查看和修改时区的方法

    下面是详细的MySQL查看和修改时区的方法: 查看当前时区 在MySQL客户端,可以通过如下命令查看当前时区: SELECT @@global.time_zone; 查看当前时区的设置,可以通过如下命令: SHOW VARIABLES LIKE ‘%time_zone%’; 输出内容包含了当前时区设置、可用的时区列表,以及系统时间和UTC时间之间的时差(即偏…

    database 2023年5月22日
    00
  • ubuntu安装mysql数据库方法

    当你使用 Ubuntu 操作系统时,想要安装 MySQL 数据库的话,可以按照以下步骤进行: 步骤一:更新 apt-get 在开始前,我们需要确保 apt-get 工具是最新的。为此,我们可以在终端中输入以下命令: sudo apt-get update 然后按下回车键就可以更新了。稍等片刻,更新就会完成。 步骤二:安装 MySQL 接下来,我们可以输入以下…

    database 2023年5月22日
    00
  • 详解分析MySQL8.0的内存消耗

    详解分析MySQL8.0的内存消耗 MySQL是使用内存进行数据缓存的关系型数据库系统。在使用MySQL时,我们需要了解MySQL的内存消耗,以便正确地配置服务器并优化性能。 MySQL 8.0的内存使用情况 MySQL 8.0改动了内存使用的方式,现在默认情况下InnoDB缓存是分配在全局映射区域(global mmap),而不再是传统的堆分配方式。 以下…

    database 2023年5月22日
    00
合作推广
合作推广
分享本页
返回顶部