我有一个多线程环境,并且每个线程都希望在表中选择一行(或者如果它不存在则插入它)并在其中增加一些内容。
基本上,每个线程都是这样的:
using (var context = new Entity.DBContext()) {
if(!context.MyTable.Any(...)) {
var obj = new MyTable() {
SomeValue = 0
};
context.MyTable.Add(obj)
}
var row = context.MyTable.SingleOrDefault(...);
row.SomeValue += 1;
context.SaveChanges();
}
示例中的问题:特定行的SomeValue = 0。 两个线程同时选择这个特定的行,它们都看到0。 - >它们都增加一次,SomeValue的最终结果为1,但我们希望它为2。
我假设紧接着另一个的线程应该等待(使用锁?)第一个线程结束。但我无法使其正常运作。
感谢。
答案 0 :(得分:1)
假设SQL Server,你可以这样做:
create table T1 (
Key1 int not null,
Key2 int not null,
Cnt int not null
)
go
create procedure P1
@Key1 int,
@Key2 int
as
merge into T1 WITH (HOLDLOCK) t
using (select @Key1 k1,@Key2 k2) s
on
t.Key1 = s.k1 and
t.Key2 = s.k2
when matched then update set Cnt = Cnt + 1
when not matched then insert (Key1,Key2,Cnt) values (s.k1,s.k2,0)
output inserted.Key1,inserted.Key2,inserted.Cnt;
go
exec P1 1,5
go
exec P1 1,5
go
exec P1 1,3
go
exec P1 1,5
go
(注意,它不一定是一个过程,我只是从一个线程调用它来显示它是如何工作的)
结果:
Key1 Key2 Cnt
----------- ----------- -----------
1 5 0
Key1 Key2 Cnt
----------- ----------- -----------
1 5 1
Key1 Key2 Cnt
----------- ----------- -----------
1 3 0
Key1 Key2 Cnt
----------- ----------- -----------
1 5 2
即使有多个线程调用它,我相信它应该序列化访问。我正在生成输出只是为了表明每个调用者也可以知道他们设置计数器的值(这里是列Cnt
),即使其他调用者之后立即更改了值。
答案 1 :(得分:0)
如果只有一个进程同时写入数据库,则可以将代码包装在C#lock(obj) {}
语句中。这限制了您只能使用一个不能充分利用数据库的活动查询,但如果可以,那么它就是一个简单的解决方案。
另一种选择是在列上创建唯一索引,以定义该行是否已存在。如果你insert
,那么你将获得duplicate key
例外。你可以在C#中使用catch
并改为运行update
。
如果您可以编写原始SQL,则可以使用锁定提示,例如with (updlock, holdlock)
或set isolation level serializable
。这可能会以复杂性为代价为您提供最佳性能。