2009-03-30 5 views
5

Mam poprzednią tabelę danych w SQL Server 2005, która ma PK bez tożsamości/autoinkrementacji i brak uprawnień do implementacji.Jak uniknąć warunku wyścigu bazy danych przy ręcznym zwiększaniu PK nowego wiersza

W wyniku tego, jestem zmuszony do ręcznego tworzenia nowych rekordów w ASP.NET poprzez "SELECT MAX (id) + 1 FROM table" - przed wstawieniem techniki.

Oczywiście tworzy to warunek wyścigu na identyfikatorze w przypadku równoczesnych wstawek.

Jaki jest najlepszy sposób, aby z wdziękiem rozwiązać zdarzenie kolizji wyścigowej? Szukam pomysłów kodu VB.NET lub C# wzdłuż linii wykrywania kolizji, a następnie ponownej próby nieudanej wstawienia, uzyskując kolejny max (id) + 1. Czy można to zrobić?

Myśli? Komentarze? Mądrość?

Dziękujemy!

UWAGA: Co, jeśli nie mogę w żaden sposób zmienić bazy danych?

Odpowiedz

4

Brak możliwości zmiany schematu bazy danych jest trudny.

Jeśli wstawisz PK do tabeli, otrzymasz SqlException z komunikatem wskazującym naruszenie ograniczeń PK Złap ten wyjątek i ponów próbę wstawienia kilka razy, aż ci się uda. Jeśli okaże się, że współczynnik kolizji jest zbyt wysoki, możesz spróbować max(id) + <small-random-int> zamiast max(id) + 1. Zauważ, że dzięki temu podejściu twoje identyfikatory będą miały luki, a przestrzeń identyfikacyjna zostanie wyczerpana wcześniej.

Innym możliwym podejściem jest emulować autoinkrementing id poza bazą danych. Na przykład utwórz statyczną liczbę całkowitą, Interlocked.Increment za każdym razem, gdy potrzebujesz następnego id i użyj zwróconej wartości. Najtrudniejszą częścią jest zainicjowanie tego statycznego licznika na dobrą wartość. Chciałbym zrobić to z Interlocked.CompareExchange: tylko

class Autoincrement { 
    static int id = -1; 
    public static int NextId() { 
    if (id == -1) { 
     // not initialized - initialize 
     int lastId = <select max(id) from db> 
     Interlocked.CompareExchange(id, -1, lastId); 
    } 
    // get next id atomically 
    return Interlocked.Increment(id); 
    } 
} 

oczywiście te ostatnie prace, jeśli wszystkie identyfikatory wstawione są uzyskiwane poprzez Autoincrement.NextId pojedynczego procesu.

+0

Właśnie to, co było mi potrzebne, dzięki !!! –

6

Utwórz pomocniczą tabelę z kolumną tożsamości. W transakcji wstaw do tabeli Aux, pobierz wartość i użyj jej do wstawienia do starej tabeli. W tym momencie możesz nawet usunąć wiersz wstawiony do tabeli Aux, a celem jest użycie go jako źródła zwiększonych wartości.

+0

To znacznie łatwiejsze do zrealizowania, +1 –

+0

Bardzo sprytny, dzięki! Niestety nie mam możliwości zmiany bazy danych. Jest zamrożone. –

+0

Dodaj go jako drugą, połączoną bazę danych - lub użyj pliku XML lokalnego dla kodu, aby osiągnąć to samo. –

1

Najlepszym rozwiązaniem jest zmiana bazy danych. Możesz nie być w stanie zmienić kolumny, aby była kolumną tożsamości, ale powinieneś być w stanie upewnić się, że w kolumnie jest unikalne ograniczenie i dodać nową kolumnę identyfikacyjną zapełnioną istniejącymi PK. Następnie użyj nowej kolumny lub użyj wyzwalacza, aby stara kolumna była lustrzana lub nowa.

3

Kluczem jest zrobienie tego w jednym oświadczeniu lub jednej transakcji.

Czy możesz to zrobić?

INSERT (PKcol, col2, col3, ...) 
SELECT (SELECT MAX(id) + 1 FROM table WITH (HOLDLOCK, UPDLOCK)), @val2, @val3, ... 

Bez testów, to prawdopodobnie będzie działać zbyt:

INSERT (PKcol, col2, col3, ...) 
VALUES ((SELECT MAX(id) + 1 FROM table WITH (HOLDLOCK, UPDLOCK)), @val2, @val3, ...) 

Jeśli nie, innym sposobem jest zrobić to w spuście.

  1. Spust jest częścią transakcji INSERT
  2. Zastosowanie HOLDLOCK, UPDLOCK na MAX. Ten posiada blokady rekordu aż popełnić
  3. wiersza aktualizowany jest zablokowana na czas

Druga wkładka będzie czekać aż do pierwszych uzupełnia. Wadą jest to, że zmieniasz klucz podstawowy.

Pomocnicza tabela musi być częścią transakcji.

Albo zmienić schemat jak sugeruje ...

+0

Próbowałem zagnieżdżonej wstawki i powiedział mi, że nie mogę zagnieżdżać wstawek. Nie mam również uprawnień do zmiany bazy danych w inny sposób niż SQL Updates/Inserts. –

+0

Czy transakcja po stronie klienta jest dozwolona w kodzie klienta? – gbn

2

co działa całą partię (wybierz dla id i wstawić) w serializable transakcji?

To powinno sprawić, że będziesz musiał dokonać zmian w bazie danych.

3

Uwaga: wszystko, czego potrzebujesz, to źródło stale rosnących liczb całkowitych. Nie musi pochodzić z tej samej bazy danych, ani nawet z bazy danych.

Osobiście użyłbym SQL Express, ponieważ jest to bezpłatne i łatwe.

Jeśli masz pojedynczy serwer internetowy: utworzyć bazę danych SQL Express na serwerze WWW z jednej tabeli [id] z jednego pola Autoinkrementacja [zmiennej new_id]. Wstaw tabelę do tej tabeli [ids], pobierz [new_id] i przekaż ją do warstwy bazy danych jako PK danej tabeli.

Jeśli masz wiele serwerów internetowych: jest to ból do instalacji, ale można użyć tej samej sztuczki, ustawiając odpowiednią Seed/przyrost (czyli przyrost = 3, a nasienie = 1/2/3 dla trzech sieci serwery).

1

Czy głównym problemem jest równoczesny dostęp? Chodzi mi o to, czy wiele wystąpień Twojej aplikacji (lub, daj Boże, innym aplikacjom poza Twoją kontrolą) będzie współdziałać z wstawkami?

Jeśli nie, możesz prawdopodobnie zarządzać wkładkami za pomocą centralnego, zsynchronizowanego modułu w aplikacji i całkowicie unikać warunków wyścigu.

Jeśli tak, to ... jak Joel powiedział, zmień bazę danych. Wiem, że nie możesz, ale problem jest tak stary jak wzgórza i został rozwiązany dobrze - na poziomie bazy danych. Jeśli chcesz to naprawić samodzielnie, będziesz musiał w pętli (wstawić, sprawdzić kolizje, usunąć) w kółko. Podstawowym problemem jest to, że nie możesz wykonać transakcji (nie mam na myśli tego w sensie SQL "TRANSACTION", ale w szerszym rozumieniu teorii danych), jeśli nie masz wsparcia z bazy danych.

Mam tylko jedną dalszą myśl, że jeśli przynajmniej masz kontrolę nad tym, kto ma dostęp do bazy danych (np. Tylko "autoryzowane" aplikacje, napisane lub zatwierdzone przez ciebie), możesz zaimplementować mutex side-band rodzajów, gdzie "gadka" jest udostępniana przez wszystkie aplikacje, a własność mutex jest wymagana do wykonania wstawki. To byłaby własna włochata kula wosku, ponieważ musiałbyś wymyślić zasady dla martwych klientów, gdzie jest hostowany, problemy z konfiguracją itp. I oczywiście "nieuczciwy" klient mógłby tworzyć wkładki bez gadającego gadka i wąż całą instalację.