Próbując oddzielić aplikację od naszej monolitycznej bazy danych, próbowaliśmy zmienić kolumny INT IDENTITY w różnych tabelach na kolumnę obliczeniową PERSISTED, która używa COALESCE. Zasadniczo potrzebujemy odsprzęgniętej aplikacji możliwości ciągłej aktualizacji bazy danych wspólnych danych dla wielu aplikacji, jednocześnie umożliwiając istniejącym aplikacjom tworzenie danych w tych tabelach bez potrzeby modyfikacji kodu lub procedury.
Zasadniczo przeszliśmy od definicji kolumny;
PkId INT IDENTITY(1,1) PRIMARY KEY
do;
PkId AS AS COALESCE(old_id, external_id, new_id) PERSISTED NOT NULL,
old_id INT NULL, -- Values here are from existing records of PkId before table change
external_id INT NULL,
new_id INT IDENTITY(2000000,1) NOT NULL
We wszystkich przypadkach PkId jest również KLUCZEM PODSTAWOWYM, a we wszystkich przypadkach oprócz jednego jest KLASTROWANY. Wszystkie tabele mają takie same klucze obce i indeksy jak poprzednio. Zasadniczo nowy format pozwala na dostarczenie PkId przez oddzieloną aplikację (jako external_id), ale także pozwala, aby PkId był wartością kolumny TOŻSAMOŚCI, umożliwiając w ten sposób istniejący kod, który opiera się na kolumnie TOŻSAMOŚĆ dzięki użyciu SCOPE_IDENTITY i @@ IDENTITY pracować tak jak kiedyś.
Problem, który mieliśmy, polegał na tym, że natrafiliśmy na kilka zapytań, które kiedyś działały w akceptowalnym czasie, aby teraz całkowicie wysadzić w powietrze. Wygenerowane plany zapytań używane przez te zapytania nie przypominają tego, czym były wcześniej.
Biorąc pod uwagę, że nowa kolumna jest KLUCZEM PIERWOTNYM, tym samym typem danych co poprzednio, i TRWAŁĄ, oczekiwałbym, że zapytania i plany zapytań będą zachowywać się tak samo jak wcześniej. Czy ZLICZONY PERSISTED INT PkId powinien zasadniczo zachowywać się tak samo, jak jawna definicja INT pod względem sposobu, w jaki SQL Server wygeneruje plan wykonania? Czy są inne prawdopodobne problemy z tym podejściem, które możesz zobaczyć?
Celem tej zmiany było umożliwienie nam zmiany definicji tabeli bez potrzeby modyfikowania istniejących procedur i kodu. Biorąc pod uwagę te problemy, nie wydaje mi się, abyśmy mogli zastosować to podejście.
źródło
Odpowiedzi:
PIERWSZY
Prawdopodobnie nie trzeba wszystkie trzy kolumny:
old_id
,external_id
,new_id
.new_id
Kolumna, byciaIDENTITY
, będzie mieć nową wartość wygenerowaną dla każdego wiersza, nawet po włożeniu doexternal_id
. Ale pomiędzyold_id
iexternal_id
są one prawie wzajemnie wykluczające: albo istnieje jużold_id
wartość, albo ta kolumna, w obecnej koncepcji, będzie tylko,NULL
jeśli użyjeszexternal_id
lubnew_id
. Ponieważ nie dodasz nowego „zewnętrznego” identyfikatora do już istniejącego wiersza (tj. Takiego, który maold_id
wartość), i nie będzie żadnych nowych wartościold_id
, więc może być używana jedna kolumna do obu celów.Pozbądź się
external_id
kolumny i zmień nazwę,old_id
aby była czymś podobnymold_or_external_id
lub czymkolwiek. Nie powinno to wymagać żadnych rzeczywistych zmian w niczym, ale zmniejsza pewne komplikacje. Konieczne może być wywołanie kolumnyexternal_id
, nawet jeśli zawiera ona „stare” wartości, jeśli kod aplikacji jest już zapisany do wstawieniaexternal_id
.Dzięki temu nowa struktura jest po prostu:
Teraz dodano tylko 8 bajtów na wiersz zamiast 12 bajtów (zakładając, że nie używasz
SPARSE
opcji ani kompresji danych). I nie trzeba było zmieniać żadnego kodu, T-SQL ani kodu aplikacji.DRUGA
Kontynuując tę ścieżkę uproszczenia, spójrzmy na to, co nam pozostało:
old_or_external_id
Kolumna albo ma już wartości, lub będą miały nową wartość z aplikacji, lub będą lewoNULL
.new_id
Zawsze będzie mieć nową wartość wygenerowaną, ale wartość ta będzie używana tylko wtedy, gdyold_or_external_id
kolumna jestNULL
.Nigdy nie ma czasu, kiedy potrzebujesz wartości zarówno w, jak
old_or_external_id
i wnew_id
. Tak, będą chwile, gdy obie kolumny będą miały wartości z powodunew_id
byciaIDENTITY
, ale tenew_id
wartości są ignorowane. Ponownie te dwa pola wzajemnie się wykluczają. Co teraz?Teraz możemy zastanowić się, dlaczego tak naprawdę potrzebowaliśmy
external_id
. Biorąc pod uwagę, że można wstawić doIDENTITY
kolumny za pomocąSET IDENTITY_INSERT {table_name} ON;
, możesz w ogóle nie wprowadzać żadnych zmian schematu, a jedynie modyfikować kod aplikacji, aby zawijaćINSERT
instrukcje / operacjeSET IDENTITY_INSERT {table_name} ON;
iSET IDENTITY_INSERT {table_name} OFF;
instrukcje. Następnie należy określić, do jakiego zakresu początkowego należy zresetowaćIDENTITY
kolumnę (dla nowo wygenerowanych wartości), ponieważ będzie ona musiała znajdować się znacznie powyżej wartości, które wstawi kod aplikacji, ponieważ wstawienie wyższej wartości spowoduje, że następna automatycznie wygenerowana wartość będzie być większa niż bieżąca wartość MAX. Ale zawsze możesz wstawić wartość poniżej wartości IDENT_CURRENT .Łączenie kolumn
old_or_external_id
inew_id
nie zwiększa również szansy na wystąpienie nakładającej się wartości między wartościami generowanymi automatycznie i wartościami generowanymi przez aplikację, ponieważ celem uzyskania 2, a nawet 3 kolumn jest połączenie ich w wartość klucza podstawowego, i są to zawsze unikalne wartości.W tym podejściu wystarczy:
Pozostaw tabele jako:
Dodaje to 0 bajtów do każdego wiersza zamiast 8, a nawet 12.
SET IDENTITY_INSERT {table_name} ON;
iSET IDENTITY_INSERT {table_name} OFF;
instrukcje.DRUGA, część B
Odmianą podejścia opisanego bezpośrednio powyżej byłoby wprowadzenie wartości wstawiania kodu aplikacji zaczynających się od -1, a następnie zmniejszających się . To pozostawia
IDENTITY
wartości jako jedyne idące w górę . Korzyścią jest to, że nie tylko nie komplikujesz schematu, ale także nie musisz się martwić, że natrafisz na nakładające się identyfikatory (jeśli wartości wygenerowane przez aplikację trafią do nowego automatycznie wygenerowanego zakresu). Jest to opcja tylko wtedy, gdy nie używasz już ujemnych wartości identyfikatora (i wydaje się, że ludzie rzadko używają wartości ujemnych w automatycznie generowanych kolumnach, więc w większości sytuacji powinno to być prawdopodobne).W tym podejściu wystarczy:
Pozostaw tabele jako:
Dodaje to 0 bajtów do każdego wiersza zamiast 8, a nawet 12.
-1
.SET IDENTITY_INSERT {table_name} ON;
iSET IDENTITY_INSERT {table_name} OFF;
instrukcje.Tutaj nadal musisz to zrobić
IDENTITY_INSERT
, ale: nie dodajesz żadnych nowych kolumn, nie musisz „ponownie wysyłać” żadnychIDENTITY
kolumn i nie ma w przyszłości ryzyka nakładania się.DRUGA, część 3
Ostatnią odmianą tego podejścia byłoby ewentualnie zamiana
IDENTITY
kolumn i zamiast tego użycie Sekwencji . Powodem takiego podejścia jest możliwość wprowadzenia wartości wstawiania kodu aplikacji, które są: dodatnie, powyżej zakresu generowanego automatycznie (nie poniżej) i nie ma takiej potrzebySET IDENTITY_INSERT ON / OFF
.W tym podejściu wystarczy:
Skopiuj
IDENTITY
kolumnę do nowej kolumny, która nie maIDENTITY
właściwości, ale maDEFAULT
Ograniczenie za pomocą funkcji NASTĘPNA WARTOŚĆ DLA :Dodaje to 0 bajtów do każdego wiersza zamiast 8, a nawet 12.
SET IDENTITY_INSERT {table_name} ON;
iSET IDENTITY_INSERT {table_name} OFF;
instrukcje.JEDNAK , ze względu na wymaganie, aby kod z jednym
SCOPE_IDENTITY()
lub@@IDENTITY
nadal działał poprawnie, przełączanie na Sekwencje nie jest obecnie opcją, ponieważ wydaje się, że nie ma odpowiednika tych funkcji dla Sekwencji :-(. Smutne!źródło
IDENTITY_INSERT
, ale nie przetestowałem tego. Nie jestem pewien, czy opcja nr 1 rozwiąże ogólny problem, była to tylko obserwacja mająca na celu zmniejszenie niepotrzebnej złożoności. Jeśli jednak masz wiele wątków wstawiających nowe „zewnętrzne” identyfikatory, jak możesz zagwarantować, że są one unikalne?IDENTITY_INSERT ON
na ten sam stół w dwóch sesjach i wkładał się do obu bez problemu.