8

このようなシナリオ: 一定量のデータがテーブルに挿入され、しきい値に達すると挿入されなくなります。このシナリオをシミュレートしました。

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
}

方法 1 - 並行性が非常に深刻

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});
            }
        }

}

方法 2 - 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});
   }
}

方法3 - ロックを使用して同時実行を破棄しますが、それが最善の解決策だとは思いません

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;
}
4

2 に答える 2

4

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
于 2012-08-27T15:17:12.867 に答える
1

これを逆に行うことを検討すると、おそらくはるかに簡単になります。例えば:

  • 自動インクリメント インデックスを持つテーブルを作成します。「Up」または「RequestOrdering」などと呼んでください。
  • クライアント要求を任意の順序で取得します。リクエストごとに:
    • Up に新しい行を挿入します。
    • 最後に挿入された行 ID を取得します。
    • 最後の挿入 ID <= 20 の場合、実際の挿入を行います。
  • アップテーブルは使い終わったら捨ててください。

データベースが自動インクリメント (IIRC、MyISAM テーブルがサポート) を含む複数列の主キーをサポートしている場合、複数の製品スペシャルで「Up」テーブルを再利用できます。


さらに簡単な実装は、"Down" に 20 行を挿入し、リクエストごとに 1 行を削除することです。影響を受けた行の数 (1 である必要があります) をチェックして、ユーザーが成功したかどうかを確認します。これは、複数の製品のスペシャルでうまく機能します。例えば:

delete * from Down where down_special_id = Xxx limit 1

おそらく最も簡単で、誰が「勝った」かを追跡するには、製品ごとに 1 つの行を作成し、各ユーザーに 1 つ (そして 1 つだけ) 行を更新させます。再度、影響を受けた行の数をチェックして、成功したかどうかを確認します。

update Up
set
  user_name = @user_name
where
  user_name is null
  and product_id = @product_id
limit 1
于 2012-08-30T22:10:42.443 に答える