【发布时间】:2013-09-30 13:23:50
【问题描述】:
在浏览时,我发现following 问题/讨论关于插入尚不存在的记录的“最佳”方法。让我印象深刻的陈述之一是 [Remus Rusanu] 说:
两种变体都不正确。您将插入重复的@value1、@value2 对,保证。
虽然我同意检查与插入“分离”的语法(并且不存在显式锁定/事务管理);我很难理解为什么以及何时对于看起来像这样的其他建议语法是正确的
INSERT INTO mytable (x)
SELECT @x WHERE NOT EXISTS (SELECT * FROM mytable WHERE x = @x);
我不想开始(另一个)什么是最好/最快的讨论,我也不认为语法可以“替换”唯一索引/约束(或 PK),但我真的需要知道这种构造在什么情况下会导致我过去一直在使用这种语法,所以我想知道将来继续这样做是否不安全。
我认为发生的情况是 INSERT 和 SELECT 都在同一个(隐式)事务中。查询将对相关记录(键)进行 IX 锁定,并且在整个查询完成之前不会释放它,因此只有在插入记录之后。 这个锁会阻止所有其他连接进行相同的 INSERT,因为在我们的插入完成之前它们自己无法获得锁;只有这样他们才能获得锁,并开始自己验证记录是否已经存在。
恕我直言,找出答案的最佳方法是通过测试,我已经在笔记本电脑上运行以下代码一段时间了:
创建表
CREATE TABLE t_test (x int NOT NULL PRIMARY KEY (x))
在下面并行运行很多很多连接)
SET NOCOUNT ON
WHILE 1 = 1
BEGIN
INSERT t_test (x)
SELECT x = DatePart(ms, CURRENT_TIMESTAMP)
WHERE NOT EXISTS ( SELECT *
FROM t_test old
WHERE old.x = DatePart(ms, CURRENT_TIMESTAMP) )
END
到目前为止,唯一需要注意的是:
- 没有遇到错误(目前)
- CPU 运行非常热 =)
- 表快速保存了 300 条记录(由于日期时间的 3 毫秒“精度”),之后不再发生实际插入,正如预期的那样。
更新:
原来我上面的例子并没有按照我的意图去做。而不是多个连接尝试同时插入相同的记录,我只是让它在第一秒后不插入已经存在的记录。由于复制粘贴和在下一个连接上执行查询可能需要大约一秒钟,所以从来没有重复的危险。剩下的时间我都戴着驴耳朵……
无论如何,我已经调整了测试以更符合手头的问题(使用同一张表)
SET NOCOUNT ON
DECLARE @midnight datetime
SELECT @midnight = Convert(datetime, Convert(varchar, CURRENT_TIMESTAMP, 106), 106)
WHILE 1 = 1
BEGIN
INSERT t_test (x)
SELECT x = DateDiff(ms, @midnight, CURRENT_TIMESTAMP)
WHERE NOT EXISTS ( SELECT *
FROM t_test old
WHERE old.x = DateDiff(ms, @midnight, CURRENT_TIMESTAMP))
END
你瞧,输出窗口现在包含大量错误
消息 2627,第 14 级,状态 1,第 8 行 违反主键约束“PK__t_test__3BD019E521C3B7EE”。无法在对象“dbo.t_test”中插入 > 重复键。重复键值为(57581873)。
仅供参考:正如 Andomar 所指出的,添加 HOLDLOCK 和/或 SERIALIZABLE 提示确实“解决”了问题,但结果却导致了很多死锁......当我认为这不是很好但也不意外它通过了。
我想我有很多代码审查要做......
【问题讨论】:
-
你刚刚用你的标题在 SO 中创建了 sql 注入
-
呵呵,等到“我的儿子”报名参加吧! (xkcd.com/327)
-
为了避免死锁,您可以将
UPDLOCK添加到组合中。这将序列化对x上的范围锁的访问。 -
@Martin Smith:同时使用
HOLDLOCK和UPDLOCK确实可以解决问题。有趣的副作用是,当工作负载由于锁定而序列化时,它将活动减少到 2 个内核 100% 运行。 -
好,除了现在有一堆额外的工作来处理一堆(旧)代码之外,我现在还困扰关于如何“拆分”点...我要投票赞成 Andomar,因为它帮助我意识到我在(坏的)测试脚本中的错误,但接受 Remus 的回答,因为它提出了一个关于这将如何失败的示例。感谢@Martin 的 UPDLOCK 指针!
标签: sql sql-server