基于C#解决库存扣减及订单创建时防止并发死锁的问题
目录
- 解决库存扣减及订单创建时防止并发死锁的问题
- 那么怎样解决死锁?
- 1. 减少事务的执行时间。
- 2. 业务锁
- 测试场景
- 通过查询库存和订单信息核对库存是否扣减正常
- 核验结果
解决库存扣减及订单创建时防止并发死锁的问题 在我们日常开发的过程可有会遇到以下错误
事务(进程 ID 82)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务很多开发人员对于这个问题的排查起来是比较困难的,而生产生的原因多种多样,很多人认是因为表中的数据太多了同时操作的人多人才会产生这种错误,下面我们来还原一下死锁的过程。
我们看一下以下sql代码(该样例代码测试环境为SqlServer)
1. 第一先创建一个测试表
H_Test
复制以下代码SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE TABLE [dbo].[H_TEST]([Id] [int] IDENTITY(1,1) NOT NULL,[DID] [int] NULL,[UNAME] [nvarchar](50) NULL,[UNAME2] [nvarchar](50) NULL,CONSTRAINT [PK_H_TEST_3994ceeb-a4b8-41e1-b06b-1e59a2e51d8c] PRIMARY KEY CLUSTERED ([Id] ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'自增主键' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_TEST', @level2type=N'COLUMN',@level2name=N'Id'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'DID' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_TEST', @level2type=N'COLUMN',@level2name=N'DID'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'UNAME' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_TEST', @level2type=N'COLUMN',@level2name=N'UNAME'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'UNAME2' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_TEST', @level2type=N'COLUMN',@level2name=N'UNAME2'GOinsert [dbo].[H_TEST](DID,UNAME,UNAME2) VALUES(1,'HI','HI2'); insert [dbo].[H_TEST](DID,UNAME,UNAME2) VALUES(2,'HISQL','HISQL2');
2. 打开两个查询窗口 在窗口1中复制以下代码
begin tran update dbo.H_TEST set UNAME='d1' where dID=1 waitfor delay '00:00:10' update H_TEST set UNAME='d2' where dID=2commit tran
在窗口2中复制以下代码
begin tran update H_TEST set UNAME='d2' where dID=2waitfor delay '00:00:10' update dbo.H_TEST set UNAME='d1' where dID=1commit tran
3. 执行代码 同时执行窗口1和窗口2的代码,在等待一段时间后你就可以看到以下错误如下所示
文章图片
通过以上的测试就还原了产生死锁的过程,刚才的测试表
H_Test
中只有两条数据,其实产生死锁与数据大小没有很大的关系,其实与整个事务的执行长短有关系,两个业务都在操作同一条数据,且一个事务中包含非常复杂的处理逻辑且执行时间比较长那么在并发或相对较多的业务操作时就会产生死锁。那么怎样解决死锁?
1. 减少事务的执行时间。 优化代码将不需要包在事务的逻辑分离出来以减少锁的占用时间.可以减少一部分的死锁,但在高并发操作时依然会产生死锁
2. 业务锁 日常我们用到的锁都是高度依赖于数据来锁定来保证数据的原子性问题,但这样有一个很大的BUG就是对数据库的性能压力非常大,在出现高并发时可能应用扛得住数据库扛不住的情况
下面介绍的就是基于
HiSql
的业务锁机制解决死锁问题,我们模拟一种场景 扣减库存并生成订单那么我们模拟创建两张表 库存表H_Stock
及订单表H_Order
表创建的sql如下HiSql怎样使用 请参照hisql快速上手
库存表sql代码
CREATE TABLE [dbo].[H_Stock]([Batch] [varchar](20) NOT NULL,[Material] [varchar](20) NOT NULL,[Location] [varchar](5) NULL,[st_kc] [decimal](18, 2) NULL,[CreateTime] [datetime] NULL,[CreateName] [nvarchar](50) NULL,[ModiTime] [datetime] NULL,[ModiName] [nvarchar](50) NULL,CONSTRAINT [PK_H_Stock] PRIMARY KEY CLUSTERED ([Batch] ASC,[Material] ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]GOALTER TABLE [dbo].[H_Stock] ADDCONSTRAINT [DF_H_Stock_st_kc]DEFAULT ((0)) FOR [st_kc]GOALTER TABLE [dbo].[H_Stock] ADDCONSTRAINT [DF_H_Stock_CreateTime]DEFAULT (getdate()) FOR [CreateTime]GOALTER TABLE [dbo].[H_Stock] ADDCONSTRAINT [DF_H_Stock_CreateName]DEFAULT ('') FOR [CreateName]GOALTER TABLE [dbo].[H_Stock] ADDCONSTRAINT [DF_H_Stock_ModiTime]DEFAULT (getdate()) FOR [ModiTime]GOALTER TABLE [dbo].[H_Stock] ADDCONSTRAINT [DF_H_Stock_ModiName]DEFAULT ('') FOR [ModiName]GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'批次号' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Stock', @level2type=N'COLUMN',@level2name=N'Batch'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'款号' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Stock', @level2type=N'COLUMN',@level2name=N'Material'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'库位' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Stock', @level2type=N'COLUMN',@level2name=N'Location'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'库存数' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Stock', @level2type=N'COLUMN',@level2name=N'st_kc'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'创建时间' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Stock', @level2type=N'COLUMN',@level2name=N'CreateTime'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'创建人' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Stock', @level2type=N'COLUMN',@level2name=N'CreateName'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'修改时间' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Stock', @level2type=N'COLUMN',@level2name=N'ModiTime'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'修改人' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Stock', @level2type=N'COLUMN',@level2name=N'ModiName'GO
订单表sql
CREATE TABLE [dbo].[H_Order]([OrderId] [bigint] NOT NULL,[Batch] [varchar](20) NOT NULL,[Material] [varchar](20) NOT NULL,[Shop] [varchar](5) NULL,[Location] [varchar](5) NULL,[SalesNum] [decimal](18, 2) NULL,[CreateTime] [datetime] NULL,[CreateName] [nvarchar](50) NULL,[ModiTime] [datetime] NULL,[ModiName] [nvarchar](50) NULL,CONSTRAINT [PK_H_Order] PRIMARY KEY CLUSTERED ([OrderId] ASC,[Batch] ASC,[Material] ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]GOALTER TABLE [dbo].[H_Order] ADDCONSTRAINT [DF_H_Order_SalesNum]DEFAULT ((0)) FOR [SalesNum]GOALTER TABLE [dbo].[H_Order] ADDCONSTRAINT [DF_H_Order_CreateTime]DEFAULT (getdate()) FOR [CreateTime]GOALTER TABLE [dbo].[H_Order] ADDCONSTRAINT [DF_H_Order_CreateName]DEFAULT ('') FOR [CreateName]GOALTER TABLE [dbo].[H_Order] ADDCONSTRAINT [DF_H_Order_ModiTime]DEFAULT (getdate()) FOR [ModiTime]GOALTER TABLE [dbo].[H_Order] ADDCONSTRAINT [DF_H_Order_ModiName]DEFAULT ('') FOR [ModiName]GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'批次号' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Order', @level2type=N'COLUMN',@level2name=N'Batch'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'款号' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Order', @level2type=N'COLUMN',@level2name=N'Material'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'门店' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Order', @level2type=N'COLUMN',@level2name=N'Shop'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'出库库位' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Order', @level2type=N'COLUMN',@level2name=N'Location'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'销售数量' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Order', @level2type=N'COLUMN',@level2name=N'SalesNum'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'创建时间' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Order', @level2type=N'COLUMN',@level2name=N'CreateTime'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'创建人' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Order', @level2type=N'COLUMN',@level2name=N'CreateName'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'修改时间' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Order', @level2type=N'COLUMN',@level2name=N'ModiTime'GOEXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=https://www.it610.com/article/N'修改人' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_Order', @level2type=N'COLUMN',@level2name=N'ModiName'GO
测试场景 开启多个线程随机产生不同的订单(一个订单中有不同批次和数量)直至库存扣减完成并检测是否有锁产生,且库存有没有少扣和超扣,如果达到这两个目标说明测试是成功的
c# 代码
class Program{static void Main(string[] args){Console.WriteLine("测试!"); StockThread(); var s = Console.ReadLine(); }static void StockThread(){//如果有安装redis可以启用以下测试一下//HiSql.Global.RedisOn = true; //开启redis缓存//HiSql.Global.RedisOptions = new RedisOptions { Host = "172.16.80.178", PassWord = "pwd123", Port = 6379, CacheRegion = "TST", Database = 0 }; HiSqlClient sqlClient = Demo_Init.GetSqlClient(); //清除库存表和订单表数据sqlClient.CodeFirst.Truncate("H_Stock"); sqlClient.CodeFirst.Truncate("H_Order"); //初始化库存数据sqlClient.Modi("H_Stock", new List
数据库连接配置
internal class Demo_Init{public static HiSqlClient GetSqlClient(){HiSqlClient sqlclient = new HiSqlClient(new ConnectionConfig(){DbType = DBType.SqlServer,DbServer = "local-HoneBI",ConnectionString = "server=(local); uid=sa; pwd=Hone@123; database=HiSql; Encrypt=True; TrustServerCertificate=True; ",//; MultipleActiveResultSets = true; User = "tansar",//可以指定登陆用户的帐号Schema = "dbo",IsEncrypt = true,IsAutoClose = true,SqlExecTimeOut = 60000,AppEvents = new AopEvent(){OnDbDecryptEvent = (connstr) =>{//解密连接字段//Console.WriteLine($"数据库连接:{connstr}"); return connstr; },OnLogSqlExecuting = (sql, param) =>{//sql执行前 日志记录 (异步)//Console.WriteLine($"sql执行前记录{sql} time:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff")}"); },OnLogSqlExecuted = (sql, param) =>{//sql执行后 日志记录 (异步)//Console.WriteLine($"sql执行后记录{sql} time:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff")}"); },OnSqlError = (sqlEx) =>{//sql执行错误后 日志记录 (异步)Console.WriteLine(sqlEx.Message.ToString()); },OnTimeOut = (int timer) =>{//Console.WriteLine($"执行SQL语句超过[{timer.ToString()}]毫秒..."); }}}); //sqlclient.CodeFirst.InstallHisql(); return sqlclient; }}
通过查询库存和订单信息核对库存是否扣减正常
select * from H_Stock select batch,sum(salesnum) as salesnum from H_Order group by batchselect orderid,sum(salesnum) as salesnum from H_Order group by orderidselect * from H_Order
核验结果
文章图片
通过测试过程可以发现 不会产生死锁也不会造成库存扣减异常保证了数据的一致性
【基于C#解决库存扣减及订单创建时防止并发死锁的问题】到此这篇关于基于C#解决库存扣减及订单创建时防止并发死锁的问题的文章就介绍到这了,更多相关c#库存扣减防止并发死锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
推荐阅读
- 基于stm32简易计算机电路图,基于STM32的简易电子计算器设计与实现(DOC) - 图文
- 基于stm32简易计算机电路图,基于STM32的简易电子计算器设计与实现.doc
- 基于stm32简易计算机电路图,基于STM32简易电子计算器设计与实现.doc
- Qt|QT(C++)连接MySQL中文乱码解决
- 人工智能无人干预垃圾发电全面解决方案
- vue.js|Java毕业设计(基于SpringBoot+Vue前后分离的社区健康疫情防控系统)
- VMware虚拟机克隆后解决网络冲突问题
- 关于ECShop管理中心无法登录的解决办法
- ubuntu21.04 simplescreenrecorder录屏没有i声音解决办法
- Web宝塔面板网站默认80端口被占用完美解决!