2012-08-24 27 views
8

このようなシナリオ:テーブルに挿入する一定量のデータ、しきい値に達しなくなったときに、このシナリオをシミュレートしました。マルチスレッドの場合は例えばasp.net)は同時に発生した問題を抱えていました。ado.netで同時実行の競合を解決する方法

私の質問は

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 lockケースを使用していない、同時問題の解決方法である - 同時実行、非常に深刻な

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 - ロックd同時をestroy、私はそれが最善の解決策

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; 
} 
+0

データの挿入に使用できないデータベースのタイプは何ですか? – Jake1164

+0

保護しようとしているシナリオは非常に不明です。おそらくあなたがしようとしていることを言い換えることはできますか? –

+0

@MarcGravell実際には私たちの電子商取引のウェブサイトの商品スパイク活動をデザインしていました。例えば、asp.netはマルチスレッドなので、商品は20個しか買えません。 :( – JeffZhnn

答えて

4

だとは思わないあなただけの20未満の行がある場合に、下に挿入したいようですね。もしそうなら:あなたは、キー範囲ロックを取得するようにしますが* 必要 *あなたは、理想的な「シリアライズ」、トランザクションを使用する必要があります場合は、

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 
+0

私はテスト(https://github.com/dushouke/ConcurrencyTest)を書いていましたが、1つの操作で問題を解決することはできませんが、 'UPDLOCK'はコンプリート – JeffZhnn

+0

を破棄できるので、 UPDLOCK'は「並行処理」を破棄しません。***は正しく***が表示されているため、同じトランザクションでこのデータを変更する予定であることを示しています(読んでいるだけではありません)。 –

+0

はい、私は2番目のバージョンをテストしました。これ以上のレコードは挿入されません。結果スクリーンショットhttp://i.stack.imgur.com/155ev.jpg – JeffZhnn

1

が逆にこれをやって考えてみましょうし、それはおそらく非常になります単純な例:。。

  • 自動インクリメントのインデックスを持つテーブルを作成し、「アップ」、または「RequestOrdering」、または何でもそれを呼び出す
  • 任意の順序でクライアント要求を取得し、各リクエストの場合:。。
    • 新しい行をUpにします。
    • 最後に挿入された行IDを取得します。
    • 最後の挿入IDが< = 20の場合は、実際の挿入を行います。
  • 終了したらアップテーブルを捨てます。

データベースが自動増分(IIRC、MyISAMテーブル)のいずれかを持つ複数列の主キーをサポートしている場合、複数の製品スペシャルにまたがって「上」テーブルを再利用できます。


さらに簡単な実装では、 "Down"に20行を挿入し、1リクエストにつき1行を削除します。影響を受けた行の数を調べて(1にする)、ユーザーが成功したかどうかを確認します。これはマルチプロダクトスペシャルでうまくいきます。たとえば、次のように

delete * from Down where down_special_id = Xxx limit 1 

おそらく最も簡単な、そしてまた、誰が「ウォン」を追跡するため、製品ごとに1つの行を作成し、各ユーザーの更新1つだけの行を持っています。また、影響を受けた行の数を確認して、成功したかどうかを確認します。

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

MySQLの構文を赦してください。 "LIMIT" – EthanB

関連する問題