【问题标题】:Parallel Inserts/Updates in a SQL Server tableSQL Server 表中的并行插入/更新
【发布时间】:2015-11-20 11:08:16
【问题描述】:

我有一个多线程环境,每个线程都想在表中选择一行(如果不存在则插入)并在其中增加一些内容。

基本上,每个线程都会做这样的事情:

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。

我假设在另一个之后到达的线程应该等待(使用锁?)第一个结束。但是我不能让它正常工作。

谢谢。

【问题讨论】:

  • 确认 - 您使用的是什么数据库系统?这可能应该在 SQL 级别解决。
  • 我同意 Damien_The_Unbeliever 的观点。您应该使用RowVersion 来确保原子访问。
  • 我使用 EntityFramework 和 SQL-Server。
  • 您看到的问题是 EF 使用单独的读/写语句并且没有可序列化的事务。如果您真的只是将字段增加 +1,那么最好使用 SP(或直接 SQL 语句)在单个更新语句中完全执行此操作。如果您尝试在实体读/写周围包装可序列化的 TX,通常会发生死锁。

标签: c# sql sql-server multithreading entity-framework


【解决方案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 列),即使随后另一个调用者立即更改了该值。

【讨论】:

  • 在多线程环境中使用merge 是一个非常糟糕的主意,请参阅Aaron Bertrand at mssqltips 我们在工作中被其中的几个问题所困扰。
  • 它实际上工作得很好,似乎在存储过程中执行插入/更新避免了插入/更新同时执行,然后考虑每个更新。谢谢 Damien_The_Unbeliever ;)
  • 最后说明:必须在 SQL 请求中使用锁(使用 (UPDLOCK, HOLDLOCK)
【解决方案2】:

如果只有一个进程同时写入数据库,您可以将代码包装在 C# lock(obj) {} 语句中。这将您限制在一个无法充分利用数据库的活动查询中,但如果没问题,这是一个简单的解决方案。

另一个选项是在定义行是否已存在的列上创建唯一索引。如果您对此进行insert,您将获得duplicate key 异常。您可以在 C# 中 catch 并运行 update

如果您可以编写原始 SQL,则可以使用锁定提示,例如 with (updlock, holdlock)set isolation level serializable。这可能会以复杂性为代价为您提供最佳性能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-29
    • 1970-01-01
    • 2012-06-16
    • 2011-02-16
    • 1970-01-01
    相关资源
    最近更新 更多