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

yizhihongxing

下面是基于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日

相关文章

  • Redis 彻底禁用RDB持久化操作

    如果你需要彻底禁用 Redis 的 RDB 持久化操作,你可以按照以下步骤操作: 打开 Redis 配置文件(比如 redis.conf),找到以下配置: save 900 1 save 300 10 save 60 10000 这里的 save 配置项定义了 RDB 持久化操作的触发条件。具体来说,当以下条件满足时,Redis 就会执行一次 RDB 持久化…

    database 2023年5月22日
    00
  • 基于mysql时间处理函数的应用详解

    基于MySQL时间处理函数的应用详解 介绍 MySQL是一种广泛使用的关系型数据库管理系统,在其SQL语言中,内建有丰富的时间处理函数,可以方便地完成时间相关的计算。本文会详细讲解一些MySQL时间处理函数的使用方法,包括DATE_FORMAT、TIMESTAMPDIFF、DATE_ADD等函数,帮助读者更好地处理时间数据,实现更加复杂的操作。 DATE_F…

    database 2023年5月22日
    00
  • Redis 的基本操作、Key的操作及命名规范

    Redis基本操作 查看数据的状态 pong redis 给我们返回 PONG,表示 redis 服务 运行正常    redis 默认用 使用 16 个 库 • Redis 默认使用 16 个库,从 0 到 15。 对数据库个数的修改, 在 redis.conf 文件中   查看当前库的key的个数 dbsize   切换库的命令    select  d…

    Redis 2023年4月13日
    00
  • Mybatis学习总结之mybatis使用建议

    针对“Mybatis学习总结之mybatis使用建议”的问题,我会结合自己的经验和学习总结,给出一些使用Mybatis时的建议和实用技巧。 1. 配置文件拆分 在Mybatis开发中,通常建议将配置文件拆分成多个小文件,方便管理和维护。拆分后我们可以选择将mapper、typeAlias等不同功能拆分成不同的配置文件,例如: mybatis-config.x…

    database 2023年5月21日
    00
  • oracle 发送邮件 实现方法

    Oracle 实现发送邮件需要使用第三方库 UTL_SMTP,该库包含在 Oracle 数据库中。以下是实现方法的完整攻略: 1. 准备工作 首先需要确认数据库服务器是否可以和外部邮件服务器通信,需要开启网络,确保能够连接 SMTP 服务器的 25 端口。还需要获取外部 SMTP 服务器的地址,账号和密码,这些信息会在后面的步骤里使用。 2. 创建存储过程 …

    database 2023年5月21日
    00
  • asp.net下Oracle,SQL Server,Access万能数据库通用类

    在ASP.NET的开发中,我们经常会遇到需要使用不同类型的数据库的情况,比如Oracle、SQL Server、Access等。为了更好地实现数据访问层的封装和代码重用,我们可以使用通用数据库访问类。本攻略中将介绍如何使用ASP.NET提供的数据库访问类,实现对不同类型数据库的访问。 1. 创建通用数据库访问类 using System.Data; usin…

    database 2023年5月21日
    00
  • Mysql数据库之sql基本语句小结

    Mysql数据库之SQL基本语句小结 Mysql是一种常用的关系型数据库,使用SQL语句进行管理和操作。在这篇文章中,我们将介绍Mysql数据库中的基本SQL语句并给出示例说明。 SQL语句的分类 在Mysql中,SQL语句主要可以分为以下几类: 数据库操作语句,包括创建、删除和选择数据库等。 数据表操作语句,包括创建、删除和修改表结构等。 数据操作语句,包…

    database 2023年5月22日
    00
  • 详解Redis数据备份和还原方法

    Redis数据备份和还原是在Redis服务器中执行的一种操作,我们通过这种操作可以将Redis数据库的内容备份到磁盘上,以便于在需要时进行还原。 Redis数据备份 Redis数据备份是通过两种方式进行的,分别是RDB和AOF。 RDB备份 RDB是Redis数据库的默认备份方式。使用RDB备份方式备份Redis数据库时,Redis会将数据库的当前状态写入到…

    Redis 2023年3月21日
    00
合作推广
合作推广
分享本页
返回顶部