我正在开发一个使用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
就足够了,而其他人声称可以进行可序列化的交易,或者您可能遇到数据完整性问题。
我希望有人可以解释为什么长时间死锁可能会发生,并详细说明在这种情况下锁定机制是什么最佳。
答案 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)
)有关事务隔离级别的更多信息: