INSERT INTO <table>
SELECT <natural keys>, <other stuff...>
FROM <table>
WHERE NOT EXISTS
-- race condition risk here?
( SELECT 1 FROM <table> WHERE <natural keys> )
UPDATE ...
WHERE <natural keys>
- det finns ett tävlingstillstånd i den första INSERT. Nyckeln kanske inte existerar under den inre frågan SELECT, men existerar vid INSERT-tidpunkten vilket resulterar i nyckelöverträdelse.
- det finns ett tävlingstillstånd mellan INSERT och UPDATE. Nyckeln kan finnas när den är markerad i den inre frågan i INSERT men är borta när UPDATE körs.
För det andra loppet kan man hävda att nyckeln ändå skulle ha raderats av den samtidiga tråden, så det är egentligen inte en förlorad uppdatering.
Den optimala lösningen är vanligtvis att pröva det mest sannolika fallet och hantera felet om det misslyckas (i en transaktion förstås):
- om nyckeln sannolikt saknas, sätt alltid i först. Hantera den unika överträdelsen av begränsningen, reserv till uppdatering.
- Om nyckeln sannolikt finns, uppdatera alltid först. Infoga om ingen rad hittades. Hantera eventuella överträdelser av unika begränsningar, reserv till uppdatering.
Förutom korrekthet är detta mönster också optimalt för hastighet:är mer effektivt att försöka infoga och hantera undantaget än att göra falska låsningar. Låsningar betyder logiska sidläsningar (vilket kan innebära fysiska sidläsningar), och IO (även logisk) är dyrare än SEH.
Uppdatera @Peter
Varför är inte ett enda påstående "atomiskt"? Låt oss säga att vi har en trivial tabell:
create table Test (id int primary key);
Om jag nu skulle köra detta enstaka uttalande från två trådar, i en loop, skulle det vara "atomiskt", som du säger, ett tillstånd utan ras kan existera:
insert into Test (id)
select top (1) id
from Numbers n
where not exists (select id from Test where id = n.id);
Men på bara ett par sekunder inträffar en primärnyckelöverträdelse:
Msg 2627, Level 14, State 1, Line 4
Brott mot PRIMARY KEY constraint 'PK__Test__24927208'. Kan inte infoga dubblettnyckel i objektet 'dbo.Test'.
Varför är det så? Du har rätt i att SQL-frågeplanen kommer att göra "rätt sak" på DELETE ... FROM ... JOIN
, på WITH cte AS (SELECT...FROM ) DELETE FROM cte
och i många andra fall. Men det finns en avgörande skillnad i dessa fall:"underfrågan" hänvisar till målet av en uppdatering eller ta bort drift. För sådana fall kommer frågeplanen verkligen att använda ett lämpligt lås, i själva verket är detta beteende kritiskt i vissa fall, som när man implementerar köer med användning av tabeller som köer.
Men i den ursprungliga frågan, såväl som i mitt exempel, ses underfrågan av frågeoptimeraren precis som en underfråga i en fråga, inte som någon speciell fråga av typen "sök efter uppdatering" som behöver speciellt låsskydd. Resultatet är att exekveringen av undersökningssökningen kan observeras som en distinkt operation av en samtidig observatör , vilket bryter det "atomära" beteendet i uttalandet. Om inte särskilda försiktighetsåtgärder vidtas kan flera trådar försöka infoga samma värde, både övertygade om att de hade kontrollerat och att värdet inte redan existerar. Bara en kan lyckas, den andra kommer att träffa PK-kränkningen. QED.