我给你一个 真实项目级的例子:
用 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 加锁并发内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论