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>
- první INSERT obsahuje spor. Klíč nemusí existovat během vnitřního dotazu SELECT, ale existuje v době INSERT, což vede k porušení klíče.
- mezi INSERT a UPDATE existuje spor. Klíč může existovat při kontrole ve vnitřním dotazu INSERT, ale v době spuštění UPDATE je pryč.
Pro druhou podmínku sporu by se dalo namítnout, že klíč by byl stejně smazán souběžným vláknem, takže to ve skutečnosti není ztracená aktualizace.
Optimálním řešením je obvykle vyzkoušet nejpravděpodobnější případ a ošetřit chybu, pokud selže (samozřejmě uvnitř transakce):
- pokud klíč pravděpodobně chybí, vždy jej vložte jako první. Ošetřete jedinečné porušení omezení a vraťte se k aktualizaci.
- pokud je klíč pravděpodobně přítomen, vždy jej nejprve aktualizujte. Vložte, pokud nebyl nalezen žádný řádek. Ošetřete možné jedinečné porušení omezení, použijte záložní aktualizaci.
Kromě správnosti je tento vzor také optimální pro rychlost:je efektivnější pokusit se vložit a zpracovat výjimku, než dělat falešné blokování. Zablokování znamená logické čtení stránky (což může znamenat čtení fyzické stránky) a IO (i logické) je dražší než SEH.
Aktualizovat @Petr
Proč není jediný výrok „atomový“? Řekněme, že máme triviální tabulku:
create table Test (id int primary key);
Nyní, pokud bych spustil tento jediný příkaz ze dvou vláken, ve smyčce, bylo by to „atomové“, jak říkáte, může existovat podmínka bez závodu:
insert into Test (id)
select top (1) id
from Numbers n
where not exists (select id from Test where id = n.id);
Přesto během několika sekund dojde k porušení primárního klíče:
Zpráva 2627, úroveň 14, stav 1, řádek 4
Porušení omezení PRIMARY KEY 'PK__Test__24927208'. Nelze vložit duplicitní klíč do objektu 'dbo.Test'.
proč tomu tak je? Máte pravdu v tom, že plán dotazů SQL udělá „správnou věc“ na DELETE ... FROM ... JOIN
, na WITH cte AS (SELECT...FROM ) DELETE FROM cte
a v mnoha dalších případech. V těchto případech je však zásadní rozdíl:„poddotaz“ odkazuje na cíl o aktualizaci nebo smazat úkon. V takových případech bude plán dotazů skutečně používat vhodný zámek, ve skutečnosti je toto chování v určitých případech kritické, například při implementaci front Používání tabulek jako front.
Ale v původní otázce, stejně jako v mém příkladu, je poddotaz vnímán optimalizátorem dotazů jen jako poddotaz v dotazu, nikoli jako nějaký speciální dotaz typu „skenovat aktualizaci“, který potřebuje speciální ochranu zámkem. Výsledkem je, že provádění vyhledávání poddotazů může být pozorováno jako samostatná operace souběžným pozorovatelem , čímž se naruší „atomové“ chování příkazu. Pokud neučiníte zvláštní opatření, může se několik vláken pokusit vložit stejnou hodnotu, obě jsou přesvědčeny, že je zkontrolovaly, a že hodnota již neexistuje. Pouze jeden může uspět, druhý zasáhne porušení PK. QED.