【问题标题】:How to solve the concurrency conflict in ado.net如何解决ado.net中的并发冲突
【发布时间】:2012-08-24 02:45:42
【问题描述】:

这样一个场景:表中要插入一定量的数据,达到阈值时不再插入,我模拟了这个场景,在多线程的情况下(如asp.net)出现并发问题。

我的问题是如何解决并发问题,不要使用lock的情况

void Main()
{

   Enumerable.Range(0,20).ToList().ForEach(i=>{
       MockMulit();
   });

}
//Start a certain number of threads for concurrent simulation
void MockMulit()
{
  int threadCount=100;

  ClearData();//delete all data for test


  var tasks=new List<Task>(threadCount);
  Enumerable.Range(1,threadCount).ToList().ForEach(i=>{
     var j=i;
     tasks.Add(Task.Factory.StartNew(()=>T3(string.Format("Thread{0}-{1}",Thread.CurrentThread.ManagedThreadId,j))));
  });
  Task.WaitAll(tasks.ToArray());

  CountData().Dump();//show that the result
}

方法一——并发很严重

void T1(string name)
{
        using(var conn=GetOpendConn())
        {
            var count=conn.Query<int>(@"select count(*)  from dbo.Down").Single();
            if(count<20)
            {
                conn.Execute(@"insert into dbo.Down (UserName) values (@UserName)",new{UserName=name});
            }
        }

}

方法二 - 将sql放在一起可以减少并发,但仍然存在

void T2(string name)
{
   using(var conn=GetOpendConn())
   {
       conn.Execute(@"
                      if((select count(*)  from dbo.Down)<20)
                        begin
                          --WAITFOR DELAY '00:00:00.100';
                          insert into dbo.Down (UserName) values (@UserName)
                        end",new{UserName=name});
   }
}

方法三 - 用锁破坏并发,但我认为这不是最好的解决方案

private static readonly object countLock=new object();
void T3(string name)
{
    lock(countLock)
    {
        using(var conn=GetOpendConn())
        {
            var count=conn.Query<int>(@"select count(*)  from dbo.Down").Single();
            if(count<20)
            conn.Execute(@"insert into dbo.Down (UserName) values (@UserName)",new{UserName=name});
        }
    }
}

其他帮助方法

//delete all data
void ClearData()
{
   using(var conn=GetOpendConn())
   {
       conn.Execute(@"delete from dbo.Down");
   }
}
//get count
int CountData()
{
   using(var conn=GetOpendConn())
   {
      return conn.Query<int>(@"select count(*)  from dbo.Down").Single();
   }
}
//get the opened connection
DbConnection GetOpendConn()
{
    var conn=new SqlConnection(@"Data Source=.;Integrated Security=SSPI;Initial Catalog=TestDemo;");
    if(conn.State!=ConnectionState.Open)
       conn.Open();
    return conn;
}

【问题讨论】:

  • 您使用的是什么类型的数据库无法处理插入数据?
  • 您试图保护的场景非常不清楚。您能否重新表述一下您正在尝试做的事情?
  • @MarcGravell 其实我是为我们的电子商务网站设计了一个商品秒杀活动。例如,一个商品只有 20 个可以购买,因为 asp.net 是多线程的,我们的数据库中出现了 21 条记录! :(

标签: c# asp.net concurrency ado.net


【解决方案1】:

听起来您只想在少于 20 行时插入 Down。如果是这样:将其作为单个操作:

insert dbo.Down (UserName)
select @UserName
where (select count(1) from dbo.Down) < 20

select @@rowount -- 1 if we inserted, 0 otherwise

或者,如果您*需要*您将需要使用事务,理想情况下是“可序列化”,以便您获得键范围锁定 - 甚至可能将 (UPDLOCK) 添加到初始计数,以确保它需要一个急切的写锁(或块,而不是死锁)。但是:单个 TSQL 操作(如已经说明的那样更可取。您甚至可以使这种操作更加偏执(尽管我不确定它是否需要它):

declare @count int

begin tran

insert dbo.Down (UserName)
select @UserName
where (select count(1) from dbo.Down (UPDLOCK)) < 20

set @count = @@rowount

commit tran

select @count -- 1 if we inserted, 0 otherwise

【讨论】:

  • 我正在写一个测试(github.com/dushouke/ConcurrencyTest),使得单个操作无法解决问题。但是用UPDLOCK可以破坏并发
  • @jefferydu 不,UPDLOCK 不会“破坏并发”——它只是正确地表明我们正计划更改此数据在同一个事务中(而不是仅仅阅读它)。你试过我发布的第二个版本吗?
  • 是的,我测试了第二个版本,没有插入更多记录。结果截图i.stack.imgur.com/155ev.jpg
【解决方案2】:

考虑反向执行此操作,它可能会简单得多。例如:

  • 创建具有自动递增索引的表。将其称为“Up”或“RequestOrdering”或其他任何名称。
  • 以任意顺序获取客户端请求。对于每个请求:
    • 在 Up 中插入一个新行。
    • 获取最后插入的行 ID。
    • 如果最后一个插入 ID
  • 用完后扔掉你的 Up 桌子。

如果您的数据库支持其中一个自动递增的多列主键(IIRC、MyISAM 表支持),您可以在多个产品特价中重复使用“Up”表。


更简单的实现是将 20 行插入“向下”并在每个请求中删除一个。检查受影响的行数(应为 1)以查看用户是否成功。这与多产品特价搭配得很好。例如:

delete * from Down where down_special_id = Xxx limit 1

这可能是最简单的,并且还可以跟踪谁“获胜”,为每个产品创建一行,并让每个用户更新一行(并且只有一行)。再次检查受影响的行数以查看它们是否成功:

update Up
set
  user_name = @user_name
where
  user_name is null
  and product_id = @product_id
limit 1

【讨论】:

  • 请原谅 MySQL 语法。我认为 SQL Server 和其他类似“TOP”与“LIMIT”。
猜你喜欢
  • 2012-12-02
  • 2020-12-17
  • 1970-01-01
  • 2012-03-01
  • 2010-10-22
  • 1970-01-01
  • 1970-01-01
  • 2015-10-15
  • 1970-01-01
相关资源
最近更新 更多