当前位置: 首页 > article >正文

go语言里面实现并发安全扣减库存的几种方式

一、基本数据准备1、数据表的创建-- ---------------- -- 库存表 -- ---------------- DROP TABLE IF EXISTS inventory; CREATE TABLE inventory ( id int NOT NULL AUTO_INCREMENT primary key COMMENT 主键id, goods_id int(11) default 1 comment 商品id, stocks int(11) default 1 comment 商品库存, version int(11) default 0 comment 商品版本号, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, deleted_at timestamp NULL DEFAULT NULL COMMENT 软删除时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存表;2、根据实体类创建数据模型3、手动在数据库中插入商品库存数据4、创建一个基本的连接gorm的方法packageutilsimport(fmt_github.com/go-sql-driver/mysqlgorm.io/driver/mysqlgorm.io/gormgorm.io/gorm/loggergorm.io/gorm/schema)varGormDb*gorm.DBfuncinit(){varerrerrorsqlStr:root:123456tcp(localhost:3306)/gorm_demo?charsetutf8mb4parseTimetruelocLocalGormDb,errgorm.Open(mysql.Open(sqlStr),gorm.Config{Logger:logger.Default.LogMode(logger.Info),//DisableForeignKeyConstraintWhenMigrating: true, // 禁止创建外键NamingStrategy:schema.NamingStrategy{SingularTable:true,// 全部的表名前面加前缀//TablePrefix: mall_,},})iferr!nil{fmt.Println(数据库连接错误,err)return}}二、模拟并发下单1、模拟并发下单扣减库存typeInventoryDtostruct{GoodsIDint64json:goodsId// 商品idNumint64json:num// 下单数量}funcSell(ws*sync.WaitGroup){reqList:make([]InventoryDto,0)reqListappend(reqList,InventoryDto{GoodsID:1,Num:1,})deferws.Done()tx:utils.GormDb.Begin()for_,item:rangereqList{// 扣减库存inventoryEntity:model.InventoryEntity{}iferr:tx.Where(goods_id ?,item.GoodsID).First(inventoryEntity).Error;err!nil{fmt.Println(查询错误)tx.Rollback()return}// 库存减少stocks:inventoryEntity.Stocks-item.Numifstocks0{fmt.Println(库存不足..)tx.Rollback()return}fmt.Println(开始扣减库存...)iferr:tx.Model(model.InventoryEntity{}).Where(goods_id ?,item.GoodsID).UpdateColumn(stocks,stocks).Error;err!nil{fmt.Println(扣减库存失败,err)tx.Rollback()return}fmt.Println(扣减库存成功...)}tx.Commit()}funcmain(){// 开始模拟下单wg:sync.WaitGroup{}fori:0;i20;i{wg.Add(1)goSell(wg)}wg.Wait()}2、查看数据库库存信息执行了20个并发但是实际没有扣减20个库存三、使用加锁的方式来实现并发安全1、使用go的锁的机制来实现并发安全packagemaintypeInventoryDtostruct{GoodsIDint64json:goodsId// 商品idNumint64json:num// 下单数量}varmsync.Mutex{}funcSell(ws*sync.WaitGroup){reqList:make([]InventoryDto,0)reqListappend(reqList,InventoryDto{GoodsID:1,Num:1,})deferws.Done()tx:utils.GormDb.Begin()for_,item:rangereqList{m.Lock()deferm.Unlock()// 扣减库存inventoryEntity:model.InventoryEntity{}iferr:tx.Where(goods_id ?,item.GoodsID).First(inventoryEntity).Error;err!nil{fmt.Println(查询错误)tx.Rollback()return}// 库存减少stocks:inventoryEntity.Stocks-item.Numifstocks0{fmt.Println(库存不足..)tx.Rollback()return}fmt.Println(开始扣减库存...)iferr:tx.Model(model.InventoryEntity{}).Where(goods_id ?,item.GoodsID).UpdateColumn(stocks,stocks).Error;err!nil{fmt.Println(扣减库存失败,err)tx.Rollback()return}fmt.Println(扣减库存成功...)}tx.Commit()}funcmain(){// 开始模拟下单wg:sync.WaitGroup{}fori:0;i20;i{wg.Add(1)goSell(wg)}wg.Wait()}2、查看数据库库存这次是每次减少20个了四、使用mysql的悲观锁来实现并发安全1、使用行锁类似这样的SELECT * FROM user WHERE id 1 FOR UPDATE;2、在gorm中使用行锁packagemaintypeInventoryDtostruct{GoodsIDint64json:goodsId// 商品idNumint64json:num// 下单数量}funcSell(ws*sync.WaitGroup){reqList:make([]InventoryDto,0)reqListappend(reqList,InventoryDto{GoodsID:1,Num:1,})deferws.Done()tx:utils.GormDb.Begin()for_,item:rangereqList{// 扣减库存inventoryEntity:model.InventoryEntity{}iferr:tx.Clauses(clause.Locking{Strength:UPDATE}).Where(goods_id ?,item.GoodsID).First(inventoryEntity).Error;err!nil{fmt.Println(查询错误)tx.Rollback()return}// 库存减少stocks:inventoryEntity.Stocks-item.Numifstocks0{fmt.Println(库存不足..)tx.Rollback()return}fmt.Println(开始扣减库存...)iferr:tx.Model(model.InventoryEntity{}).Where(goods_id ?,item.GoodsID).UpdateColumn(stocks,stocks).Error;err!nil{fmt.Println(扣减库存失败,err)tx.Rollback()return}fmt.Println(扣减库存成功...)}tx.Commit()}funcmain(){// 开始模拟下单wg:sync.WaitGroup{}fori:0;i20;i{wg.Add(1)goSell(wg)}wg.Wait()}3、使用悲观锁的另外一种实现方式packagemaintypeInventoryDtostruct{GoodsIDint64json:goodsId// 商品idNumint64json:num// 下单数量}funcSell(ws*sync.WaitGroup){reqList:make([]InventoryDto,0)reqListappend(reqList,InventoryDto{GoodsID:1,Num:1,})deferws.Done()tx:utils.GormDb.Begin()for_,item:rangereqList{fmt.Println(开始扣减库存...)result:tx.Model(model.InventoryEntity{}).Where(goods_id ? and stocks ?,item.GoodsID,item.Num).Update(stocks,gorm.Expr(stocks - ?,item.Num))ifresult.Error!nil{fmt.Println(扣减库存失败,result.Error)tx.Rollback()return}fmt.Println(扣减库存成功...)}tx.Commit()}funcmain(){// 开始模拟下单wg:sync.WaitGroup{}fori:0;i20;i{wg.Add(1)goSell(wg)}wg.Wait()}五、使用mysql的乐观锁来实现1、官方插件optimisticlock2、修改数据库模型version的数据类型constTableNameInventoryEntityinventory// InventoryEntity 库存表typeInventoryEntitystruct{IDint64gorm:column:id;type:int;primaryKey;autoIncrement:true;comment:主键id json:id,string// 主键idGoodsIDint64gorm:column:goods_id;type:int;default:1;comment:商品id json:goodsId// 商品idStocksint64gorm:column:stocks;type:int;default:1;comment:商品库存 json:stocks// 商品库存Version optimisticlock.Versiongorm:column:version;type:int;comment:商品版本号 json:version// 商品版本号CreatedAt LocalTimegorm:column:created_at;comment:创建时间 json:createdAt// 创建时间UpdatedAt LocalTimegorm:column:updated_at;comment:更新时间 json:updatedAt// 更新时间DeletedAt gorm.DeletedAtgorm:column:deleted_at;type:timestamp;comment:软删除时间 json:-// 软删除时间}// TableName InventoryEntitys table namefunc(*InventoryEntity)TableName()string{returnTableNameInventoryEntity}3、具体乐观锁的实现packagemaintypeInventoryDtostruct{GoodsIDint64json:goodsIdNumint64json:num}funcSell(ws*sync.WaitGroup){deferws.Done()reqList:[]InventoryDto{{GoodsID:1,Num:1},}for_,item:rangereqList{for{entity:model.InventoryEntity{}iferr:utils.GormDb.Where(goods_id ?,item.GoodsID).First(entity).Error;err!nil{fmt.Println(查询错误:,err)break}stocks:entity.Stocks-item.Numifstocks0{fmt.Println(库存不足..)break}fmt.Println(开始扣减库存...)result:utils.GormDb.Model(model.InventoryEntity{}).Where(goods_id ? AND version ?,item.GoodsID,entity.Version).Updates(map[string]interface{}{stocks:stocks,version:gorm.Expr(version 1),})ifresult.Error!nil{fmt.Println(更新错误:,result.Error)}ifresult.RowsAffected!0{break}}}}funcmain(){wg:sync.WaitGroup{}fori:0;i20;i{wg.Add(1)goSell(wg)}wg.Wait()}六、使用redis分布式锁来实现1、文档地址2、简单的案例实现packagemainimport(fmtsynctimegoredislibgithub.com/go-redis/redis/v8github.com/go-redsync/redsync/v4github.com/go-redsync/redsync/v4/redis/goredis/v8)funcmain(){// 配置redis连接client:goredislib.NewClient(goredislib.Options{Addr:localhost:6379,})// 关闭客户端连接deferclient.Close()// 创建 redsync 需要的池pool:goredis.NewPool(client)// 创建 redsync 实例rs:redsync.New(pool)// 设置锁名称mutexname:my-global-mutexgNum:2varwg sync.WaitGroup wg.Add(gNum)fori:0;igNum;i{gofunc(idint){deferwg.Done()mutex:rs.NewMutex(mutexname,redsync.WithExpiry(10*time.Second),redsync.WithTries(50),// 重试直到成功redsync.WithRetryDelay(200*time.Millisecond))fmt.Printf(goroutine %d: 开始获取锁...\n,id)iferr:mutex.Lock();err!nil{fmt.Printf(goroutine %d: 获取锁失败: %v\n,id,err)return}fmt.Printf(goroutine %d: 获取锁成功...\n,id)time.Sleep(time.Second*5)fmt.Printf(goroutine %d: 开始释放锁...\n,id)ifok,err:mutex.Unlock();!ok||err!nil{fmt.Printf(goroutine %d: 释放锁失败: %v\n,id,err)return}fmt.Printf(goroutine %d: 释放锁成功\n,id)}(i)}wg.Wait()fmt.Println(主进程结束..)}3、简单粗暴的实现packagemainimport(fmtgin-admin-api/modelgoredislibgithub.com/go-redis/redis/v8github.com/go-redsync/redsync/v4github.com/go-redsync/redsync/v4/redis/goredis/v8sync)typeInventoryDto1struct{GoodsIDint64json:goodsIdNumint64json:num}// DeductStock1 使用Redis分布式锁扣减库存优化版funcDeductStock1(ws*sync.WaitGroup){client:goredislib.NewClient(goredislib.Options{Addr:localhost:6379,})pool:goredis.NewPool(client)// or, pool : redigo.NewPool(...)rs:redsync.New(pool)// 锁名要加上单号mutex:rs.NewMutex(cell_order)iferr:mutex.Lock();err!nil{fmt.Println(获取锁失败)return}tx:utils.GormDb.Begin()deferws.Done()reqList:[]InventoryDto1{{GoodsID:1,Num:2},}for_,item:rangereqList{inventoryEntity:model.InventoryEntity{}iferr:utils.GormDb.Where(goods_id ?,item.GoodsID).First(inventoryEntity).Error;err!nil{fmt.Println(查询错误)tx.Rollback()break}ifinventoryEntity.Stocksitem.Num{fmt.Printf(库存不足剩余%d个按实际扣减%d个\n,inventoryEntity.Stocks,item.Num)tx.Rollback()break}fmt.Println(开始扣减库存...)stocks:inventoryEntity.Stocks-item.Num result:tx.Model(model.InventoryEntity{}).Where(goods_id ?,item.GoodsID).Updates(map[string]interface{}{stocks:stocks,})ifresult.Error!nil{fmt.Println(扣减库存失败,result.Error)tx.Rollback()break}}tx.Commit()// 先提交事务mutex.Unlock()}funcmain(){wg:sync.WaitGroup{}// 模拟20并发fori:0;i20;i{wg.Add(1)goDeductStock1(wg)}wg.Wait()fmt.Println(全部执行完成)}

相关文章:

go语言里面实现并发安全扣减库存的几种方式

一、基本数据准备 1、数据表的创建 -- ---------------- -- 库存表 -- ---------------- DROP TABLE IF EXISTS inventory; CREATE TABLE inventory (id int NOT NULL AUTO_INCREMENT primary key COMMENT 主键id,goods_id int(11) default 1 comment 商品id,stocks int(11) de…...

基于RetinaFace的课堂考勤系统:人脸识别与数据分析

基于RetinaFace的课堂考勤系统:人脸识别与数据分析 1. 为什么传统点名方式正在被智能考勤替代 早上八点的教室里,老师站在讲台前翻着花名册,学生低头刷手机,后排有人悄悄把书包放在空座位上——这种场景在高校和职业院校并不少见…...

贾子科学定理(Kucius Science Theorem)的哲学批判与理论重构:从证伪主义到可持续运行的科学范式研究

贾子科学定理(Kucius Science Theorem)的哲学批判与理论重构:从证伪主义到可持续运行的科学范式研究1. 引言1.1 研究背景与问题提出当代科学哲学正处于深刻的范式转换期。传统的波普尔证伪主义面临着前所未有的理论困境和实践挑战&#xff0c…...

终端设备可靠性检测报告:读懂设备耐用密码

日常使用手机、智能手表、家用路由器等终端设备时,我们总希望它“扛造耐用”,不轻易出故障。这份终端设备可靠性检测报告,就用通俗的话拆解设备耐用的核心密码,让大家明白,一台靠谱的设备,背后都经过了哪些…...

HsMod:55+创新功能重新定义炉石传说体验

HsMod:55创新功能重新定义炉石传说体验 【免费下载链接】HsMod Hearthstone Modification Based on BepInEx 项目地址: https://gitcode.com/GitHub_Trending/hs/HsMod 🌟 项目核心价值概述 HsMod作为基于BepInEx框架的炉石传说模改插件&#xf…...

统计数据时,sql执行超时,如何处理

在工作中,除了开发,有时还需要做一些数据统计。 统计数据时,sql执行超时。 可以通过以下手段处理。 一、优化sql 首先,通过 EXPLAIN 查看执行计划,看有没有走索引,能加索引的加索引,没有走索引…...

windows系统部署funrec项目:安装WSL2

注意:WSL系统与Windows系统环境是完全隔离开的,只有代码文件可以互通 windows的anaconda、python、uv、torch、tensorflow等,WSL都不能用,都需要另外安装 WSL 可以访问 Windows 的项目文件(比如 /mnt/d/MyProject/……...

突破性方案:智能引擎助力黑苹果EFI自动生成

突破性方案:智能引擎助力黑苹果EFI自动生成 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 你是否曾在深夜对着满屏的ACPI补丁代码感到迷茫…...

C++ 子数组位运算结果 题型

或运算 898. 子数组按位或操作 - 力扣(LeetCode) 我们直接看题,意思很明显,就是找出所有子数组,然后将子数组各个数相或得到的结果有多少个不同。 这里我们首先想到的就是直接把所有子数组求出来在或起来&#xff0c…...

网站SEO推广需要多少钱_如何选择合适的网站 SEO 推广服务商

网站SEO推广需要多少钱_如何选择合适的网站 SEO 推广服务商 一、了解网站SEO推广的基本概念 在当今的数字时代,网站SEO推广(Search Engine Optimization,搜索引擎优化)已成为任何企业在互联网上获得流量和客户的关键手段之一。S…...

基于下垂控制的光储直流微电网模型 1.模型由光伏和储能以及直流负载组成 2.光伏采用扰动观测法...

基于下垂控制的光储直流微电网模型1.模型由光伏和储能以及直流负载组成 2.光伏采用扰动观测法实现最大功率输出,储能刚开始采用恒定电压控制,电压稳定在额定电压附近,2s之后采用下垂控制,母线电压降低,达到目标光伏板在…...

如何处理Java LocalDateTime与Oracle TIMESTAMP WITH TIME ZONE的时区对应

根本原因是LocalDateTime无时区信息,JDBC驱动按JVM时区(如Asia/Shanghai)将其解释为带偏移时间点;存UTC时间须用localDateTime.atZone(ZoneOffset.UTC).toOffsetDateTime()显式指定偏移。Oracle插入时TIMESTAMP WITH TIME ZONE字段…...

CSS移动端解决阴影遮挡效果_利用box-shadow设置外扩散距离

box-shadow外扩散失效主因是父容器overflow隐藏、层叠上下文触发或参数误设;需检查overflow/transform/filter影响,用translateZ(0)强制分层,伪元素移出阴影,合理组合inset与外扩,并控制扩散距离≤8px。box-shadow 外扩…...

实现鼠标滚轮在容器滚动到底部后无缝过渡到页面滚动

本文介绍如何通过 javascript 检测固定高度溢出容器的滚动边界,在用户滚至底部时立即触发页面滚动,消除原生行为中约1秒的延迟等待,实现平滑、无中断的滚动接力。 本文介绍如何通过 javascript 检测固定高度溢出容器的滚动边界&#xff…...

IndexTTS 2.0应用案例:如何用它快速生成有声书和播客内容

IndexTTS 2.0应用案例:如何用它快速生成有声书和播客内容 1. 引言:声音创作的新范式 在数字内容爆炸式增长的今天,有声书和播客市场正以每年20%以上的速度扩张。但高质量音频内容的制作却面临两大痛点:专业配音成本高昂&#xf…...

[具身智能-218]:针对不同编程语言和应用场景,AI自动编程擅长与不擅长之处?

AI自动编程的能力在不同编程语言和应用场景下表现出显著差异。选择合适组合,能让AI成为强大的“加速器”,反之则可能带来风险。 核心原则是:AI对主流语言和标准化任务的支持最好,而在处理底层、高性能或复杂业务逻辑时则需要人工…...

细说杨乃武与小白菜案

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、案件二、精神分析学---心理防御机制三、关于我自己总结前言 一、案件 略,后面补 二、精神分析学—心理防御机制 在这个案件我主要关注县令和小…...

5个步骤搭建P2P视频分发系统:PCDN实战指南

5个步骤搭建P2P视频分发系统:PCDN实战指南 【免费下载链接】PCDN PCDN is an Peer to peer CDN for video, its Hybrid CDN/P2P Architecture. HTTP Live Streaming, WebRTC, videojs and peerjs, HLS and Video for broadcasts 项目地址: https://gitcode.com/g…...

DDrawCompat:让经典软件重获新生的兼容性解决方案

DDrawCompat:让经典软件重获新生的兼容性解决方案 【免费下载链接】DDrawCompat DirectDraw and Direct3D 1-7 compatibility, performance and visual enhancements for Windows Vista, 7, 8, 10 and 11 项目地址: https://gitcode.com/gh_mirrors/dd/DDrawCompa…...

数字化转型架构下的数据安全治理指南:以数据安全为核心的安全立体防御体系、数据安全体系、数据安全现状评估报告···(附相关资料)

微信公众号:木木自由,更多数据分析,经营分析、财务分析、商业分析、数据治理、数据要素、数据资产干货以及资料分享木木自由 数据分析领地Digital Technology Summit在数字经济深度发展的今天,数字化转型已成为企业生存与发展的…...

C语言完美演绎6-21

/* 范例&#xff1a;6-21 */#include<stdio.h> #include<conio.h>int main(){int n;printf("这是nn乘法表&#xff0c;请输入一值>");scanf("%d",&n);int i1;for(;i<n;) /* i从1到n次循环*/{int j1;for(;j<n;) /…...

c语言完美演绎6-20

/* 范例&#xff1a;6-20 */#include<stdio.h> #include<conio.h>int main(){int a;printf("请输入你的分数0-100>");scanf("%d",&a);if((a>0) && (a<60))printf("你被当了");else if((a>60) && (a…...

seo关键词挖掘工具哪个好_seo数据分析工具哪个最强

选择最佳SEO关键词挖掘工具和SEO数据分析工具指南 SEO关键词挖掘工具哪个好 在当今数字营销的竞争激烈环境中&#xff0c;选择合适的SEO关键词挖掘工具至关重要。这不仅能帮助你找到最相关、最受欢迎的关键词&#xff0c;还能显著提升你的网站流量和搜索引擎排名。市面上哪些…...

Unity游戏插件加载器MelonLoader完全指南:从安装到精通

Unity游戏插件加载器MelonLoader完全指南&#xff1a;从安装到精通 【免费下载链接】MelonLoader The Worlds First Universal Mod Loader for Unity Games compatible with both Il2Cpp and Mono 项目地址: https://gitcode.com/gh_mirrors/me/MelonLoader 在Unity游戏…...

Godot 4 2D 物理引擎位置初始化踩坑:add_child() 和 position 到底谁先? (错误位置触发物理事件)

Godot 4 2D 物理引擎位置初始化踩坑&#xff1a;add_child() 和 position 到底谁先&#xff1f; 在 Godot 4 做 2D 游戏时&#xff0c;很多人都会遇到一个很诡异的问题&#xff1a; 我明明想把一个 PackedScene 实例生成在 B 点&#xff0c;结果它却会在默认位置 A 点 短暂触发…...

实战演练:基于快马平台与方锐理念构建短视频智能配乐应用

最近在做一个短视频创作的小工具&#xff0c;发现给视频配乐真是个技术活。正好看到网易方锐的AI音乐技术挺火的&#xff0c;就想着能不能用它的理念做个智能配乐助手。在InsCode(快马)平台上试了试&#xff0c;没想到还真搞出了一个能跑起来的demo&#xff0c;分享下我的实现思…...

Project AirSim避障实战:深度图分割与动态航向规划详解

1. 深度图避障的核心原理 深度图避障是无人机自主导航中最基础也最关键的环节之一。简单来说&#xff0c;它就像给无人机装上了一双能精确测距的"眼睛"。这双眼睛看到的不是普通照片&#xff0c;而是一张每个像素都带有距离信息的特殊图像——我们称之为深度图&#…...

告别编译噩梦:用VSCode + CMake Tools 在Windows上优雅地构建和调试ncnn项目

告别编译噩梦&#xff1a;用VSCode CMake Tools 在Windows上优雅地构建和调试ncnn项目 对于习惯使用轻量级现代编辑器的开发者来说&#xff0c;在Windows平台编译ncnn这类高性能神经网络框架往往意味着要在笨重的IDE和晦涩的命令行工具之间艰难抉择。本文将展示如何通过VSCode…...

多头注意力机制详解:如何提升模型表达能力并减少计算复杂度

多头注意力机制详解&#xff1a;如何提升模型表达能力并减少计算复杂度 在深度学习领域&#xff0c;注意力机制已经成为提升模型性能的关键技术之一。特别是多头注意力机制&#xff0c;它通过并行处理多个注意力头&#xff0c;不仅增强了模型捕捉不同特征子空间的能力&#xff…...

生态安全格局分析第一步:如何为你的ArcGIS版本(10.0-10.8/Pro)正确配对Linkage Mapper和Circuitscape?

生态安全格局分析工具链的版本兼容性全解析&#xff1a;从ArcGIS到Linkage Mapper的精准匹配 当你在深夜的办公室里盯着屏幕&#xff0c;反复尝试让Linkage Mapper与Circuitscape协同工作时&#xff0c;是否曾因版本不匹配而遭遇令人崩溃的错误提示&#xff1f;作为生态安全格局…...