减库存问题 实践

16 篇文章 0 订阅
9 篇文章 0 订阅
本文通过示例展示了在SQL Server中使用存储过程处理并发订单创建时遇到的死锁问题。作者尝试了不同的事务隔离级别和加锁方式,但仍然存在大量死锁。面对高并发场景,作者提出将解决并发问题转向程序层面,并计划学习消息队列(MQ)来进一步处理这一挑战。博客讨论了并发控制在数据库和应用程序中的重要性,以及如何影响用户体验。
摘要由CSDN通过智能技术生成

我用sql server 做了 一个模拟 

一张订单表 

一张产品表

逻辑是准备一个存储过程   产品数量>下单数   就可以 往 订单表insert 一条记录   产品数量 -=下单数  ;

这个 存储过程 没添加 事务 没指定 会话隔离级别 也没有 加锁

ALTER  PROCEDURE [dbo].[pro_createorder_auto]
@num int,
@errormsg nvarchar(1000) output
AS
BEGIN
DECLARE @res int;
 
	  BEGIN TRY
	   --         set transaction  isolation level  repeatable read ;
				--begin tran
					if   exists(select 1 from T_goods  where id = 1 and [goods_num] >=@num)
					begin
						update T_goods   set goods_num -=@num    where id  =1;
						insert into t_orders (goods_id,goods_num)values( 1,@num );
						select @res = 1;
					end
					    select @res = 0;	
				--commit tran;
				return @res; 	

		END TRY-----------结束捕捉异常
		BEGIN CATCH------------有异常被捕获
		 select  @errormsg = error_message() -- 将异常信息付给 output 参数
			IF @@TRANCOUNT > 0---------------判断有没有事务
			BEGIN
				ROLLBACK TRAN----------回滚事务
			END 
			return 0; 	
		END CATCH--------结束异常处理
	
END

然后用C# 模拟并发 :用 1-到4的随机数下单

代码:

 class Program
    {
        static void Main(string[] args)
        {

            // 只有10万个商品  多线程模拟并发下单 100 万次
            var radom = new Random();
            var list = new List<IAsyncResult>();
            for (int i = 0; i < 10 * 10000; i++)
            {
                var doSomething = new Action(() => { CreateOrder1(radom.Next(1,5)); });
                var res = doSomething.BeginInvoke(null,null);
                list.Add(res);
            }

            while (list.Exists(x=>x.IsCompleted== false))
            {
                Console.WriteLine("还没执行完 再等等!");
                Thread.Sleep(10000);
            }
          
            Console.WriteLine("执行完毕!!!");
            Console.WriteLine("库存数量" + GoodsNum());
            Console.WriteLine("订单数量" + OrderNum());
            Console.ReadLine();
        }

        static int SccceedNum { get; set; }
        static int ErrorNum { get; set; }
        static int OtherNum { get; set; }
        static private readonly object lockobj = new object();

        /// <summary>
        /// 不加控制的生成订单,只要库存>0
        /// </summary>
        static void CreateOrder1(int num)
        {
                var pram = new List<SqlParameter>() { new SqlParameter("@num",num )};
                var temp = new SqlParameter("@errormsg", SqlDbType.NVarChar, 1000 );
                temp.Direction = ParameterDirection.Output;
                pram.Add(temp);
                var returnInt = new SqlParameter("res", SqlDbType.Int);
                returnInt.Direction = ParameterDirection.ReturnValue;
                pram.Add(returnInt);
                int res = 0;
                res = sqlHelper.ExecuteNonQuery(CommandType.StoredProcedure, "pro_createorder_auto", pram.ToArray());
                if (!string.IsNullOrWhiteSpace(  temp.Value.ToString()))
                {
                    Console.WriteLine("结果:{0},错误消息:{1}", res.ToString(), temp.Value.ToString());
                }
        }

        /// <summary>
        /// 查询当前库存数
        /// </summary>
        /// <returns></returns>
        static string GoodsNum()
        {
            return  sqlHelper.ExecuteScalar("select goods_num   from T_goods where id = 1 ").ToString();
        }

        /// <summary>
        /// 查询当前订单数
        /// </summary>
        /// <returns></returns>
        static string OrderNum()
        {
            return sqlHelper.ExecuteScalar("select  count(1) num   from T_orders where  goods_id = 1 ").ToString();
        }
    }

最后 肾宝 溢出了 12单 

 

 

后面打算在 存储过程中 加上  隔离界别  和 事务  

ALTER  PROCEDURE [dbo].[pro_createorder_auto]
@num int,
@errormsg nvarchar(1000) output
AS
BEGIN
DECLARE @res int;
 
	  BEGIN TRY
	            set transaction  isolation level  repeatable read ;
				begin tran
					if   exists(select 1 from T_goods  where id = 1 and [goods_num] >=@num)
					begin
						update T_goods   set goods_num -=@num    where id  =1;
						insert into t_orders (goods_id,goods_num)values( 1,@num );
						select @res = 1;
					end
					    select @res = 0;	
				commit tran;
				return @res; 	

		END TRY-----------结束捕捉异常
		BEGIN CATCH------------有异常被捕获
		 select  @errormsg = error_message() -- 将异常信息付给 output 参数
			IF @@TRANCOUNT > 0---------------判断有没有事务
			BEGIN
				ROLLBACK TRAN----------回滚事务
			END 
			return 0; 	
		END CATCH--------结束异常处理
	
END

现在发现 死锁太频繁了可怕  虽然订单数一直在增长 ,但是死锁的几率太大了

这要是做成 产品 肯定会被骂死吧  这代表能否 下单成功是靠运气的 ,一旦别人在下单 我就会失败,严重影响购买者的心情

我再 改成 事务 加 with (holdlock) 的方式 结果是一样的 大量死锁 ,看来在高并发的时候给数据库事务加锁也不是一个好办法

那我想用 程序的方式来实现一下锁 这边的存储过程暂时不改

程序中以后  就不会在数据库中争夺资源了,但是有大量并发时 我就是那第10万个 我的 页面得 loading 多久呀

有时候想想还不如直接有个结果 ,行就行不行就不行 ,老是 loading 耐心有限呀

到这 以什么姿势来处理并发 成了我的疑问

等我后续学一下 mq 再回来 看看这个问题

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值