【mysql库存不一致性 20个用户同样对一件商品买了2件 结果扣减的数量不对】
有注释写的详细
模拟高并发扣减库存
批量扣减库存 - 一个用户开始扣减 要么全成功 要么全失败 不能有一半扣减成功一半扣减失败(事务)
这个bug好几天没搞明白了,模拟20个用户同时批量扣减2件 只有第一件有bug
已知事务的bug 移除事务会恢复正常
package main
import (
goredislib "github.com/go-redis/redis/v8"
"github.com/go-redsync/redsync/v4"
"github.com/go-redsync/redsync/v4/redis/goredis/v8"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"fmt"
"log"
"os"
"sync"
"time"
)
//表结构自己生成一下并添加数据
/*
添加数据: Goods:421,Stocks:100
Goods:422,Stocks:100
Goods:423,Stocks:100
Goods:424,Stocks:100
*/
type BaseModel struct {
ID int32 `gorm:"primary_key;comment:ID" json:"id"`
CreatedAt time.Time `gorm:"column:add_time;comment:创建时间" json:"-"`
UpdatedAt time.Time `gorm:"column:update_time;comment:更新时间" json:"-"`
DeletedAt gorm.DeletedAt `gorm:"comment:删除时间" json:"-"`
IsDeleted bool `gorm:"comment:是否删除" json:"-"`
}
type Inventory struct {
BaseModel
Goods int32 `gorm:"type:int;index;comment:商品id"`
Stocks int32 `gorm:"type:int;comment:仓库"`
Version int32 `gorm:"type:int;comment:分布式锁-乐观锁"` //这个没用到 可以不要
}
var DB *gorm.DB
var RedisRs *redsync.Redsync
func InitDB() {
//自己的mysql 辛苦一下建个数据库:utf8mb4
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
"mysql用户名", "mysql密码", "mysql的IP建议localhost", mysql端口建议3306, "数据库名称")
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
//LogLevel: logger.Info, // 日志级别
LogLevel: logger.Silent, // 日志级别
//IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 禁用彩色打印
},
)
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
Logger: newLogger,
})
if err != nil {
panic(err)
}
}
//初始化redis
func InitRedis() {
client := goredislib.NewClient(&goredislib.Options{
//自己的redis
Addr: fmt.Sprintf("%s:%d", "IP", Port),
})
pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
RedisRs = redsync.New(pool)
}
type GoodsInvInfo struct {
GoodsId int32
Num int32
}
//批量扣减库存 - 一个用户开始扣减 要么全成功 要么全失败 不能有一半扣减成功一半扣减失败(事务)
func inventory(goods []GoodsInvInfo) {
//事务开始
tx := DB.Begin()
for _, good := range goods {
//redis锁初始化
mutex := RedisRs.NewMutex(fmt.Sprintf("goods_%d", good.GoodsId))
//获取锁
if err := mutex.Lock(); err != nil {
//return nil, status.Errorf(codes.Internal, "获取redis分布式锁异常")
panic(err)
}
//库存信息
var inv Inventory
//查询库存是否存在
if result := DB.Where(&Inventory{Goods: good.GoodsId}).First(&inv); result.RowsAffected == 0 {
panic("库存不存在")
}
//查询库存是否充足
if inv.Stocks < good.Num {
panic("库存不足")
}
//扣减库存
inv.Stocks -= good.Num
tx.Save(&inv)
//释放锁
if ok, err := mutex.Unlock(); !ok || err != nil {
tx.Rollback() //回滚之前的操作
panic(err)
}
/*
这里就会有bug了
在扣减第一件商品后另一个用户在获取第一件商品的时候数量还是扣减之前的 为什么???
只有第一件会发生扣减少的情况 其他的商品不会
只有第一件?
*/
}
tx.Commit()
}
func main() {
//初始化redis 和 mysql
InitDB()
InitRedis()
//设置20个协程来模拟用户
gNum := 20
var wg sync.WaitGroup
wg.Add(gNum)
for i := 0; i < gNum; i++ {
go func() {
defer wg.Done()
//每个用户批量购买商品2件
inventory([]GoodsInvInfo{
{GoodsId: 421, Num: 2},
{GoodsId: 422, Num: 2},
{GoodsId: 423, Num: 2},
{GoodsId: 424, Num: 2},
})
fmt.Println("扣减成功")
}()
}
wg.Wait()
}