mysql + nest.js  加锁搞并发问题的实现

 更新时间:2026年03月08日 08:29:55   作者:张洪权  
本文主要介绍了使用NestJS和MySQL实现秒杀系统的库存扣减,并解决了高并发超卖问题,通过原子SQL、NestJS原生SQL实现以及使用Redis和消息队列等方案,可以有效提高系统的性能和稳定性,

我给你一个 真实项目级的例子
NestJS + MySQL 原生 SQL 实现 秒杀库存扣减,并解决 高并发超卖问题

这个例子是很多电商系统的核心逻辑。

一、数据库设计

商品表

CREATE TABLE goods (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(100),
  stock INT NOT NULL,
  price DECIMAL(10,2),
  create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

订单表

CREATE TABLE orders (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT,
  goods_id INT,
  create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

初始化库存

INSERT INTO goods (name, stock, price)
VALUES ('iphone 15', 10, 8999);

二、问题:高并发超卖

如果代码这样写:

const goods = await db.query('SELECT * FROM goods WHERE id = ?', [id])

if (goods.stock > 0) {
  await db.query('UPDATE goods SET stock = stock - 1 WHERE id = ?', [id])
}

1000 个请求会出现:

库存10
卖出去100

因为:

读取库存不是原子操作

三、正确方案一(推荐):原子 SQL

最推荐的写法:

UPDATE goods
SET stock = stock - 1
WHERE id = ? AND stock > 0;

优点:

单条 SQL
原子操作
不会超卖
性能极高

四、NestJS 原生 SQL 实现

使用 MySQL 连接池。

安装:

npm install mysql2

数据库服务

import { Injectable } from '@nestjs/common'
import * as mysql from 'mysql2/promise'

@Injectable()
export class MysqlService {

  pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: '123456',
    database: 'test',
    connectionLimit: 10
  })

  async query(sql: string, params?: any[]) {
    const [rows] = await this.pool.execute(sql, params)
    return rows
  }

}

五、秒杀 Service

import { Injectable } from '@nestjs/common'
import { MysqlService } from './mysql.service'

@Injectable()
export class SeckillService {

  constructor(private db: MysqlService) {}

  async buy(userId: number, goodsId: number) {

    // 1 扣库存(原子SQL)
    const result: any = await this.db.query(
      `UPDATE goods 
       SET stock = stock - 1 
       WHERE id = ? AND stock > 0`,
      [goodsId]
    )

    if (result.affectedRows === 0) {
      return {
        message: '库存不足'
      }
    }

    // 2 创建订单
    await this.db.query(
      `INSERT INTO orders (user_id, goods_id) VALUES (?, ?)`,
      [userId, goodsId]
    )

    return {
      message: '秒杀成功'
    }

  }

}

六、Controller

import { Controller, Post, Body } from '@nestjs/common'
import { SeckillService } from './seckill.service'

@Controller('seckill')
export class SeckillController {

  constructor(private seckillService: SeckillService) {}

  @Post('buy')
  async buy(@Body() body) {

    const { userId, goodsId } = body

    return this.seckillService.buy(userId, goodsId)

  }

}

七、执行流程(高并发)

1000个请求:

UPDATE goods SET stock = stock - 1 WHERE id=1 AND stock>0

MySQL 内部会保证:

库存10

前10个请求成功
990个失败

不会出现:

stock = -1

八、真实大厂架构(完整版)

真正秒杀系统通常是这样:

用户请求
   ↓
Nginx
   ↓
Redis 判断库存
   ↓
消息队列 (Kafka / RabbitMQ)
   ↓
NestJS 消费
   ↓
MySQL 扣库存

作用:

Redis      防止打爆数据库
MQ         削峰
MySQL      最终一致

九、如果必须用 MySQL Lock

有些复杂业务必须用:

SELECT * FROM goods WHERE id = 1 FOR UPDATE;

NestJS 示例:

const conn = await this.db.pool.getConnection()

try {

  await conn.beginTransaction()

  const [goods]: any = await conn.query(
    'SELECT * FROM goods WHERE id = ? FOR UPDATE',
    [goodsId]
  )

  if (goods.length === 0 || goods[0].stock <= 0) {
    await conn.rollback()
    return { message: '库存不足' }
  }

  await conn.query(
    'UPDATE goods SET stock = stock - 1 WHERE id = ?',
    [goodsId]
  )

  await conn.query(
    'INSERT INTO orders (user_id, goods_id) VALUES (?, ?)',
    [userId, goodsId]
  )

  await conn.commit()

  return { message: '购买成功' }

} catch (err) {

  await conn.rollback()
  throw err

} finally {

  conn.release()

}

十、什么时候用哪种方案

方案推荐度场景
原子 UPDATE⭐⭐⭐⭐⭐秒杀、库存
事务 + FOR UPDATE⭐⭐⭐复杂业务
Redis + MQ⭐⭐⭐⭐⭐⭐超高并发

💡 我再告诉你一个 90% 后端不知道的优化技巧:

MySQL 秒杀最强写法其实是:

UPDATE goods 
SET stock = stock - 1 
WHERE id = 1 AND stock > 0
LIMIT 1;

原因:

防止误更新
锁范围更小

到此这篇关于mysql + nest.js 加锁搞并发问题的实现的文章就介绍到这了,更多相关mysql nest.js 加锁并发内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • IDEA链接MySQL报错08001和连接成功后不显示表的问题及解决方法

    IDEA链接MySQL报错08001和连接成功后不显示表的问题及解决方法

    这篇文章主要介绍了IDEA链接MySQL报错08001和连接成功后不显示表的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • MySQL创建用户的三种方法

    MySQL创建用户的三种方法

    在对MySQL的日常管理和操作中,为了避免有人恶意使用root用户控制数据库,尽可能地不用或少用 root 用户登录系统,本文主要介绍了MySQL创建用户的三种方法,感兴趣的可以了解一下
    2023-08-08
  • MySQL版本选择与安装超详细教程

    MySQL版本选择与安装超详细教程

    本文主要介绍了MySQL5.5和MySQL8.0的优点和缺点,并建议大多数用户使用最新的稳定版本,此外还提供了MySQL的安装教程和环境变量的配置方法,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-10-10
  • 在Windows主机上定时备份远程VPS(CentOS)数据的批处理

    在Windows主机上定时备份远程VPS(CentOS)数据的批处理

    我想在自己的 Windows7 下每天/周运行一次备份,就有了这个小工具
    2012-05-05
  • MySQL系列之七 MySQL存储引擎

    MySQL系列之七 MySQL存储引擎

    存储引擎是数据库的核心,对于mysql来说,存储引擎是以插件的形式运行的。虽然mysql支持种类繁多的存储引擎,但是常用的就那么几种。这篇文章主要给大家介绍MySQL存储引擎的相关知识,一起看看吧
    2021-07-07
  • 关于MYSQL的优化全面详解

    关于MYSQL的优化全面详解

    一直用了那么久的mysql,虽然了解了一些优化方法,但是都是比较简单的一些应用,这次就系统的了解一下
    2012-09-09
  • MySql如何解决mysql没有root用户问题

    MySql如何解决mysql没有root用户问题

    文章主要描述了在MySQL中没有root用户的情况如何解决,首先,作者提到由于没有root用户,无法进行后续的权限设置和操作,然后,作者详细介绍了创建root用户并赋予其所有权限的步骤,包括解决可能遇到的错误,最后,作者总结了整个过程,并希望大家能够从中受益
    2025-02-02
  • 深入理解MySQL中的行级锁

    深入理解MySQL中的行级锁

    行级锁加锁规则比较复杂,不同的场景,加锁的形式是不同的,本文主要介绍了深入理解MySQL中的行级锁,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • MySQL数据库基本SQL语句教程之高级操作

    MySQL数据库基本SQL语句教程之高级操作

    对MySQL数据库的查询,除了基本的查询外,有时候需要对查询的结果集进行处理,下面这篇文章主要给大家介绍了关于MySQL数据库基本SQL语句教程之高级操作的相关资料,需要的朋友可以参考下
    2022-06-06
  • 详解监听MySQL的binlog日志工具分析:Canal

    详解监听MySQL的binlog日志工具分析:Canal

    Canal主要用途是基于MySQL数据库增量日志解析,提供增量数据订阅和消费,目前主要支持MySQL。接下来通过本文给大家介绍监听MySQL的binlog日志工具分析:Canal的相关知识,感兴趣的朋友一起看看吧
    2020-10-10

最新评论