Can INSERT (x) VALUES (@x) WHERE NOT EXISTS ( SELECT * FROM WHERE x = @x)
cause duplicates?
While browsing SO I found the following Question/Discussion about the
"best" approach for inserting records that don't exist yet. One of the
statements that struck me was one of [Remus Rusanu] stating:
Both variants are incorrect. You will insert pairs of duplicate @value1,
@value2, guaranteed.
Although I do agree about this for the syntax where the check is
'separated' from the INSERT (and no explicit locking/transaction mgmt is
present); I'm having a hard time understanding why and when this would be
true for the other proposed syntax that looks like this
INSERT INTO mytable (x)
SELECT @x WHERE NOT EXISTS (SELECT * FROM mytable WHERE x = @x);
I do NOT want to start (another) what's best/fastest discussion, nor do I
think the syntax can 'replace' a unique index/constraint (or PK) but I
really need to know in what situations this construction could cause
doubles as I've been using this syntax in the past and wonder if it is
unsafe to continue doing so in the future.
What I think that happens is that the INSERT & SELECT are both in the same
(implicit) transaction. The query will take an IX lock on the related
record (key) and not release it until the entire query has finished, thus
only AFTER the record has been inserted. This lock blocks all other
connections from making the same INSERT as they can't get a lock
themselves until after our insert has finished; only then they get the
lock and will start verifying for themselves if the record already exists
or not.
As IMHO the best way to find out is by testing, I've been running the
following code for a while on my laptop:
-- (create table once, run below on many, many connections in parallel)
CREATE TABLE t_test (x int NOT NULL PRIMARY KEY (x))
GO
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
So far the only things to note are:
No errors encountered (yet)
CPU is running quite hot =)
table held 300 records quickly (due to 3ms 'precision' of datetime) after
that no actual inserts are happening any more, as expected.
No comments:
Post a Comment