我用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 再回来 看看这个问题