Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Atomic UPSERT w SQL Server 2005

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>
  • w pierwszej INSERT występuje sytuacja wyścigu. Klucz może nie istnieć podczas wewnętrznego zapytania SELECT, ale istnieje w czasie INSERT, co powoduje naruszenie klucza.
  • pomiędzy INSERT i UPDATE występuje wyścig. Klucz może istnieć, gdy jest sprawdzany w wewnętrznej kwerendzie INSERT, ale znika przed uruchomieniem UPDATE.

W przypadku drugiego wyścigu można by argumentować, że klucz i tak zostałby usunięty przez współbieżny wątek, więc nie jest to tak naprawdę utracona aktualizacja.

Optymalnym rozwiązaniem jest zwykle wypróbowanie najbardziej prawdopodobnego przypadku i obsługa błędu, jeśli się nie powiedzie (oczywiście w ramach transakcji):

  • jeśli prawdopodobnie brakuje klucza, zawsze wstawiaj jako pierwszy. Obsłuż unikatowe naruszenie ograniczeń, wróć do aktualizacji.
  • jeśli klucz jest prawdopodobnie obecny, zawsze najpierw aktualizuj. Wstaw, jeśli nie znaleziono żadnego wiersza. Obsłuż możliwe naruszenie ograniczeń unikatowych, wróć do aktualizacji.

Oprócz poprawności, ten wzór jest również optymalny dla szybkości:bardziej efektywne jest próbowanie wstawienia i obsługi wyjątku niż robienie fałszywych blokad. Blokady oznaczają logiczne odczyty stron (co może oznaczać fizyczne odczyty stron), a operacje we/wy (nawet logiczne) są droższe niż SEH.

Aktualizacja @Piotr

Dlaczego pojedyncza instrukcja nie jest „atomowa”? Załóżmy, że mamy banalną tabelę:

create table Test (id int primary key);

Teraz, gdybym uruchomił tę pojedynczą instrukcję z dwóch wątków, w pętli, byłoby to „atomowe”, jak mówisz, nie może istnieć żaden warunek wyścigu:

  insert into Test (id)
    select top (1) id
    from Numbers n
    where not exists (select id from Test where id = n.id); 

Jednak w ciągu zaledwie kilku sekund dochodzi do naruszenia klucza podstawowego:

Msg 2627, poziom 14, stan 1, wiersz 4
Naruszenie ograniczenia klucza podstawowego „PK__Test__24927208”. Nie można wstawić zduplikowanego klucza w obiekcie „dbo.Test”.

Dlaczego? Masz rację, że plan zapytań SQL wykona „właściwą rzecz” na DELETE ... FROM ... JOIN , na WITH cte AS (SELECT...FROM ) DELETE FROM cte iw wielu innych przypadkach. W takich przypadkach jest jednak zasadnicza różnica:„podzapytanie” odnosi się do celu aktualizacji lub usuń operacja. W takich przypadkach plan zapytań rzeczywiście użyje odpowiedniej blokady, w rzeczywistości to zachowanie jest krytyczne w niektórych przypadkach, na przykład podczas implementacji kolejek Używanie tabel jako kolejek.

Ale w pierwotnym pytaniu, a także w moim przykładzie, podzapytanie jest postrzegane przez optymalizator zapytań jako podzapytanie w zapytaniu, a nie jako specjalne zapytanie typu „skanowanie w celu aktualizacji”, które wymaga specjalnej ochrony przed blokadą. W rezultacie wykonanie podzapytania może być obserwowane jako odrębna operacja przez współbieżnego obserwatora , łamiąc w ten sposób „atomowe” zachowanie instrukcji. O ile nie zostaną podjęte specjalne środki ostrożności, wiele wątków może próbować wstawić tę samą wartość, obaj przekonani, że sprawdzili, a wartość już nie istnieje. Tylko jeden może odnieść sukces, drugi uderzy w naruszenie PK. QED.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Co to jest @@MAX_PRECISION w programie SQL Server?

  2. Jak mogę zrobić klucz podstawowy jako AUTOINCREMENT?

  3. warunek UNION ALL vs OR w zapytaniu serwera sql

  4. Tabela przestawna serwera SQL Server z agregacjami wielu kolumn

  5. Jak SESSION_CONTEXT() działa w SQL Server