我需要运行一个“获取”或“创建”记录的SQL Server查询。很简单,但我不确定这是否会产生任何竞争条件。
我已经阅读了一些关于插入/更新期间锁定的文章,例如: http://weblogs.sqlteam.com/dang/archive/2007/10/28/Conditional-INSERTUPDATE-Race-Condition.aspx,但我不确定这是否与我相关,因为我正在使用SQL Server 2012,而且我没有对修改本身的争论,只是确保我的记录“一次性创建” 。
另外,我将对Id
列(不是标识列)有一个主键约束,所以我知道最坏的竞争条件只能创建一个错误,而不是无效的数据,但我也是不希望命令抛出错误。
有人可以说明我需要解决这个问题吗?或者我在想这个,我可以简单地做一些事情:
IF EXISTS(SELECT * FROM Table WHERE Id = @Id)
BEGIN
SELECT Id, X, Y, Z FROM Table WHERE Id = @Id
END
ELSE
BEGIN
INSERT INTO Table (Id, X, Y, Z)
VALUES (@Id, @X, @Y, @Z);
SELECT @Id, @x, @Y, @Z;
END
我已经在文档数据库领域呆了几年而且我的SQL非常生疏。
答案 0 :(得分:3)
您的代码肯定有竞争条件。
一种方法是将条件合并为一个insert
,但我不能100%确定这可以防止所有竞争条件,但这可能有效:
INSERT INTO Table(Id, X, Y, Z)
SELECT @Id, @X, @Y, @Z
WHERE NOT EXISTS (SELECT 1 FROM Table WHERE ID = @ID);
SELECT Id, X, Y, Z FROM Table WHERE ID = @Id;
问题在于重复检测期间的基础锁定机制与插入。您可以尝试使用更明确的锁定,但表锁定肯定会降低处理速度(要了解锁定,您可以转到文档或博客文章,例如this)。
我认为使这项工作始终如一的最简单方法是使用try
/ catch
块:
BEGIN TRY
INSERT INTO Table(Id, X, Y, Z)
SELECT @Id, @X, @Y, @Z;
END TRY
BEGIN CATCH
END CATCH;
SELECT Id, X, Y, Z FROM Table WHERE ID = @Id;
即尝试insert
。如果它失败(可能是因为重复的密钥,但你可以明确地检查它),那么忽略失败。之后返回id的值。
答案 1 :(得分:3)
GET的发生频率远高于CREATE,因此优化它是有意义的。
IF EXISTS (SELECT * FROM MyTable WHERE Id=@Id)
SELECT Id, X, Y, Z FROM MyTable WHERE Id=@Id
ELSE
BEGIN
BEGIN TRAN
IF NOT EXISTS (SELECT * FROM MyTable WITH (HOLDLOCK, UPDLOCK) WHERE Id=@Id )
BEGIN
-- WAITFOR DELAY '00:00:10'
INSERT INTO MyTable (Id, X, Y, Z) VALUES (@Id, @X, @Y, @Z)
END
SELECT Id, X, Y, Z FROM MyTable WHERE Id=@Id
COMMIT TRAN
END
如果您的记录不存在,请开始显式交易。 需要HOLDLOCK和UPDLOCK(感谢@MikaelEriksson)。表提示在事务持续期间保持共享锁和更新锁。这样可以正确地序列化INSERT并可靠地避免竞争条件。
要验证,请取消注释WAITFOR
并同时运行两个或多个查询。
答案 2 :(得分:0)
为什么你需要两个不同的SELECT
?
IF NOT EXIST (SELECT TOP 1 1 FROM Table WHERE Id = @Id)
INSERT INTO Table(Id, X, Y, Z)
VALUES (@Id, @X, @Y, @Z)
SELECT Id, X, Y, Z FROM Table WHERE ID = @Id
只要你在检查存在时没有指定WITH (NOLOCK)
,你应该没事。