在 SQL 中使用双重检查锁定死锁
Posted
技术标签:
【中文标题】在 SQL 中使用双重检查锁定死锁【英文标题】:Deadlock using double-checked locking in SQL 【发布时间】:2013-11-28 10:48:46 【问题描述】:我的 SQL 语句遇到死锁,我想在其中选择一个 ID(如果存在),否则插入然后选择它。正如here 所建议的那样,我正在使用双重检查锁定来防止锁定开销。
显然我这样做是为了支持并发插入,并且我正在运行多个线程。我的 SQL 知识非常低,所以我可能错过了一些关于锁定的基本知识?这是我的程序:
CREATE PROCEDURE InsertAndOrSelectZipCity
@PostalDistrict nvarchar(25),
@CityName nvarchar(34),
@MunicipalityId smallint,
@ZipCode smallint
AS
DECLARE @id AS INT
SELECT @id = ZipCityId FROM ZipCity (NOLOCK) WHERE MunicipalityID=@MunicipalityId AND ZipCode=@ZipCode
IF @id IS NULL
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT @id = ZipCityId FROM ZipCity WHERE MunicipalityID=@MunicipalityId AND ZipCode=@ZipCode
IF @id IS NULL
BEGIN
INSERT INTO ZipCity (PostalDistrict, CityName, MunicipalityId, ZipCode) VALUES (@PostalDistrict, @CityName, @MunicipalityId, @ZipCode)
SELECT @id = SCOPE_IDENTITY()
END
COMMIT TRANSACTION
END
SELECT @id
更新
这可以通过在事务内的 Select 语句上使用适当的锁(XLOCK、ROWLOCK、HOLDLOCK)来解决。
以下是使用 MERGE 语句编写的过程,不需要事务:
DECLARE @id as INT
MERGE INTO ZipCity WITH (TABLOCK) AS Target
USING (SELECT @PostalDistrict, @CityName, @MunicipalityId, @ZipCode) AS Source (PostalDistrict, CityName, MunicipalityId, ZipCode)
ON Target.MunicipalityId = Source.MunicipalityId AND Target.ZipCode = Source.ZipCode
WHEN MATCHED THEN
UPDATE SET @id = Target.ZipCityId
WHEN NOT MATCHED THEN
INSERT (PostalDistrict, CityName, MunicipalityId, ZipCode) VALUES (@PostalDistrict, @CityName, @MunicipalityId, @ZipCode)
OUTPUT INSERTED.ZipCityId;
【问题讨论】:
【参考方案1】: SELECT @id = ZipCityId
FROM ZipCity
WHERE MunicipalityID=@MunicipalityId
AND ZipCode=@ZipCode
在这里,您的选择是获取 S 锁。这可能发生在多个线程上。稍后,插入尝试 X 锁定,这是一个死锁。
立即获取 X 锁:
SELECT @id = ZipCityId
FROM ZipCity WITH (XLOCK, ROWLOCK, HOLDLOCK) ...
在这里,ROWLOCK, HOLDLOCK
不是严格要求,但模式 XLOCK, ROWLOCK, HOLDLOCK
是相当标准的,我尝试在任何地方都遵循它以保持一致性。
顺便说一句,您可能想要切换到 MERGE
语句。我认为它会自动获取 U 锁,因此不需要锁定提示。不过,不确定。无论如何,这将是代码改进和性能改进。
【讨论】:
成功了 - 谢谢!对于 MERGE 建议,如果我的数据是静态的,是否必须使用 UPDATE 语句?例如。每个 ZipCity 行始终相同。 MERGE 可以包含您想要的任何子句组合。你可以只拥有一个 WHEN NOT MATCHED THEN INSERT,没有别的。 Merge 是所有其他 DML 语句的超集。 太棒了。使用 MERGE,之后我是否只需要 SELECT (NOLOCK) ZipCityID 来实现相同的输出和功能? 您使用 OUTPUT 子句:OUTPUT INSERTED.ZipCityId
。这会为您发出一个结果集。您还可以将结果流式传输到表变量或临时表中。 MERGE 是解决此类问题的整体解决方案。 SCOPE_IDENTITY 不需要(因为合并可以同时发出多个 ID,单个值可能不够)。
我刚刚测试过您可以使用以下方法提取现有 ID:WHEN MATCHED THEN UPDATE SET @id = target.ID
其中@id
是一些已声明的变量。【参考方案2】:
嗯,很难重现你的场景,但看起来确实很有趣。
尝试使用 ROWLOCK,即做这样的事情
SELECT @id = ZipCityId FROM ZipCity WITH(ROWLOCK) WHERE MunicipalityID=@MunicipalityId AND ZipCode=@ZipCode
看看它是否有帮助(我希望它有帮助)。
另外,您可能想查看这篇文章并查看 如果它与您的场景相关。在我看来是。
http://support.microsoft.com/kb/323630
【讨论】:
仅使用行锁仍然会出现死锁。 usr 使用 (XLOCK, ROWLOCK, HOLDLOCK) 的答案似乎有效。谢谢你的链接。以上是关于在 SQL 中使用双重检查锁定死锁的主要内容,如果未能解决你的问题,请参考以下文章