高并发upsert导致死锁和查询时间> 30年代

时间:2017-02-08 10:14:47

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

我正在开发一个使用EF6进行大多数数据库操作的应用程序,并且在大多数情况下,数据库内容是非关键的,并且不会承受很多负载。这个规则有一个例外,我们有一个可能有100 / s事件的流,需要根据列值插入或更新特定表中的行。

我对SQL有点弱,但是我已经编写了这个查询来插入或更新并返回元素的id:

DECLARE @Id [int];

MERGE {tableName} WITH (UPDLOCK) AS target
USING (SELECT @MatchName AS id) AS source
ON source.id = target.MatchColumn
WHEN MATCHED
    THEN UPDATE SET @Id = target.Id, ...
WHEN NOT MATCHED
    THEN INSERT (...) VALUES (...);

IF @Id IS NULL
BEGIN
    SELECT @Id = CAST(SCOPE_IDENTITY() as [int]);
END;
SELECT @Id;

它在(EF)可序列化事务块内部完成,它是在显式事务中执行的唯一事物,并且是更新此表的唯一代码。 (其他事情可以阅读)。如果数据库回滚事务(EF抛出异常),则会立即重试3次。

问题在于,当我们开始进入更高负载的情况时,我们可能最终处于这样的状态:许多事情都在尝试更新数据库,并且针对此表的查询可能开始花费30多秒(对其他表的查询仍然存在精细)。我的印象是,即使这在可序列化的事务中执行,它也只会锁定由合并匹配表达式选择的行,这应该是一个相对快速的操作。

过去几天我一直在做一些研究,有些人建议在默认交易中只有HOLDLOCK就足够了,而其他人声称可以进行可序列化的交易,或者您可能遇到数据完整性问题。

我希望有人可以解释为什么长时间死锁可能会发生,并详细说明在这种情况下锁定机制是什么最佳。

1 个答案:

答案 0 :(得分:0)

默认情况下,merge获取updlocks,因此with (updlock)没有为您做任何事情。将updlock更改为holdlock(或serializable)并在事务中运行语句将保证在操作期间获取并保留正确的锁。

  

为了防止并发会话使用相同的密钥插入数据,必须获取不兼容的锁,以确保只有一个会话可以读取密钥,并且必须保持该锁,直到事务完成。

您需要使用set transaction isolation level serializable吗?

在这种情况下,如果上面的代码完全在事务中,我认为通过将事务隔离级别显式设置为serializable不会有任何区别。 merge with (holdlock)默认会获取更新锁,它与共享锁不兼容,使用holdlock提示解决了竞争条件问题,如Dan Guzman在上面引用的文章和摘录中所解释的那样。 / p>

with (holdlock)table hint。表提示会覆盖语句的默认行为。

如果您的事务中有其他语句,那么这些语句将受到事务隔离级别与默认隔离级别或显式设置set transaction isolation level(会话级别)的差异的影响,除非被表提示覆盖。

最高粒度赢得:

  • 最低:数据库(默认为read committed
  • 中间:会话(set transaction isolation level ...
  • 最高:表提示(with (updlock, serializable)

有关事务隔离级别的更多信息: