2012-08-24 32 views
8

Kịch bản như vậy: một lượng dữ liệu nhất định được chèn vào bảng, khi đạt đến ngưỡng không còn chèn nữa, tôi mô phỏng trường hợp này, trong trường hợp đa luồng (ví dụ asp.net) xuất hiện các vấn đề đồng thời.Cách giải quyết xung đột đồng thời trong ado.net

Câu hỏi của tôi là làm thế nào để giải quyết các vấn đề đồng thời, không sử dụng các trường hợp 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 
} 

một phương pháp - đồng thời rất nghiêm trọng

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

} 

phương pháp hai - đặt sql cùng nhau có thể giảm đồng thời, nhưng vẫn tồn tại

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

phương pháp ba - với khóa d estroy sự đồng thời, nhưng tôi không nghĩ rằng đó là một giải pháp tốt nhất

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

phương pháp giúp đỡ khác

//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

Bạn đang sử dụng loại cơ sở dữ liệu nào không thể xử lý dữ liệu chèn? – Jake1164

+0

Kịch bản bạn đang cố gắng bảo vệ chống lại là rất không rõ ràng. Bạn có thể nói lại những gì bạn đang cố gắng làm không? –

+0

@MarcGravell Trên thực tế tôi đã thiết kế một hoạt động tăng đột biến hàng hóa cho ví dụ website.For thương mại điện tử của chúng tôi, một Hàng hóa chỉ có 20 có thể mua, bởi vì asp.net là đa luồng, 21 hồ sơ xuất hiện trong cơ sở dữ liệu của chúng tôi! :( – JeffZhnn

Trả lời

4

Có vẻ như bạn chỉ muốn chèn vào Down khi có ít hơn 20 dòng. Nếu như vậy: làm cho điều đó một thao tác đơn giản:

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

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

Ngoài ra, nếu bạn * cần * bạn sẽ cần phải sử dụng một giao dịch, lý tưởng "Serializable", do đó bạn sẽ có được một chìa khóa tầm-lock - có lẽ thậm chí thêm (UPDLOCK) vào số lượng ban đầu, để đảm bảo nó có một khóa viết mong muốn (hoặc khối, chứ không phải là deadlocks). Nhưng: hoạt động TSQL duy nhất (như đã minh họa là một lợi thế Bạn thậm chí có thể làm cho điều đó hoang tưởng hơn (mặc dù tôi không chắc chắn nó cần nó):.

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

Tôi đã viết một bài kiểm tra (https://github.com/dushouke/ConcurrencyTest), làm cho một thao tác đơn lẻ không thể giải quyết được vấn đề. Nhưng với 'UPDLOCK' có thể phá hủy đồng thời – JeffZhnn

+0

@jefferydu no,' UPDLOCK' không "hủy bỏ đồng thời" - nó chỉ đơn giản là *** chính xác *** chỉ ra rằng chúng tôi đang có kế hoạch thay đổi dữ liệu này trong cùng một giao dịch (thay vì chỉ đọc nó). Bạn đã thử phiên bản thứ hai tôi đã đăng chưa? –

+0

vâng, tôi đã thử nghiệm phiên bản thứ hai, không có thêm hồ sơ nào được chèn vào. Ảnh chụp màn hình kết quả http://i.stack.imgur.com/155ev.jpg – JeffZhnn

1

Cân nhắc làm điều này ngược lại và nó có lẽ sẽ nhiều . đơn giản Ví dụ:

  • Tạo một bảng với một chỉ số tự động incrementing Gọi nó là "Up", hoặc "RequestOrdering", hoặc bất cứ điều gì
  • Nhận yêu cầu của khách hàng trong bất kỳ thứ tự Đối với mỗi yêu cầu:...
    • Chèn mới hàng vào Up.
    • Nhận ID hàng được chèn cuối cùng.
    • Nếu ID chèn cuối cùng < = 20, hãy chèn thực.
  • Vứt bỏ bảng Lên khi bạn đã hoàn tất.

Nếu cơ sở dữ liệu của bạn hỗ trợ các khóa chính nhiều cột với một trong số chúng tự động tăng (IIRC, bảng MyISAM), bạn có thể sử dụng lại bảng "Lên" trên nhiều sản phẩm đặc biệt.


Triển khai dễ dàng hơn nữa sẽ chèn 20 hàng vào "Xuống" và xóa một hàng cho mỗi yêu cầu. Kiểm tra số hàng bị ảnh hưởng (phải là 1) để xem liệu người dùng có thành công hay không. Điều này chơi độc đáo với nhiều sản phẩm đặc biệt.Ví dụ:

delete * from Down where down_special_id = Xxx limit 1 

Có lẽ đơn giản nhất, và cũng để theo dõi những người "chiến thắng", tạo ra một hàng cho mỗi sản phẩm và có mỗi lần cập nhật sử dụng một (và chỉ một) liên tiếp. Một lần nữa, kiểm tra số hàng bị ảnh hưởng để xem liệu chúng có thành công hay không:

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

Tha thứ cú pháp MySQL. Tôi nghĩ SQL Server và các phiên bản khác giống như "TOP" "GIỚI HẠN". – EthanB

Các vấn đề liên quan