Kontekst
Projektuję bazę danych (na PostgreSQL 9.6), która będzie przechowywać dane z aplikacji rozproszonej. Ze względu na rozproszony charakter aplikacji, nie mogę używać liczb całkowitych z automatycznym przyrostem ( SERIAL
) jako mojego klucza głównego ze względu na potencjalne warunki wyścigu.
Naturalnym rozwiązaniem jest użycie UUID lub globalnie unikalnego identyfikatora. Postgres ma wbudowany UUID
typ , który idealnie pasuje.
Problem, który mam z UUID, jest związany z debugowaniem: jest to ciąg nieprzyjazny dla człowieka. Identyfikator ff53e96d-5fd7-4450-bc99-111b91875ec5
nic mi nie mówi, a ACC-f8kJd9xKCd
chociaż nie gwarantuje, że jest unikalny, mówi mi, że mam do czynienia z ACC
przedmiotem.
Z punktu widzenia programowania często debuguje się zapytania aplikacji dotyczące kilku różnych obiektów. Załóżmy, że programista nieprawidłowo szuka obiektu ACC
(konta) w ORD
tabeli (kolejności). Za pomocą identyfikatora czytelnego dla człowieka programista natychmiast identyfikuje problem, a korzystając z identyfikatorów UUID spędziłby trochę czasu, zastanawiając się, co jest nie tak.
Nie potrzebuję „gwarantowanej” unikalności UUID; I nie potrzeba trochę miejsca do generowania kluczy bez konfliktów, lecz UUID jest przesadą. Ponadto, w najgorszym przypadku, nie byłoby końca świata, gdyby doszło do kolizji (baza danych ją odrzuca, a aplikacja może się zregenerować). Tak więc, biorąc pod uwagę kompromisy, mniejszy, ale przyjazny dla człowieka identyfikator byłby idealnym rozwiązaniem dla mojego przypadku użycia.
Identyfikacja obiektów aplikacji
Identyfikator, który wymyśliłem, ma następujący format:, {domain}-{string}
gdzie {domain}
jest zastępowany przez domenę obiektową (konto, zamówienie, produkt) i {string}
jest losowo generowanym ciągiem. W niektórych przypadkach sensowne może być wstawienie {sub-domain}
przed losowym ciągiem. Zignorujmy długość {domain}
i {string}
w celu zagwarantowania wyjątkowości.
Format może mieć stały rozmiar, jeśli pomaga w wydajności indeksowania / zapytań.
Problem
Wiedząc to:
- Chcę mieć klucze podstawowe w formacie podobnym do
ACC-f8kJd9xKCd
. - Te klucze podstawowe będą częścią kilku tabel.
- Wszystkie te klucze będą używane w kilku sprzężeniach / relacjach w bazie danych 6NF.
- Większość tabel będzie miała rozmiar od średniego do dużego (średnio ~ 1 mln wierszy; największe z ~ 100 mln wierszy).
Jeśli chodzi o wydajność, jaki jest najlepszy sposób na przechowywanie tego klucza?
Poniżej cztery możliwe rozwiązania, ale ponieważ mam niewielkie doświadczenie z bazami danych, nie jestem pewien, która (jeśli w ogóle) jest najlepsza.
Rozważane rozwiązania
1. Zapisz jako string ( VARCHAR
)
(Postgres nie robi różnicy między CHAR(n)
i VARCHAR(n)
, więc ignoruję CHAR
).
Po kilku badaniach odkryłem, że porównanie ciągów VARCHAR
, szczególnie w operacjach łączenia, jest wolniejsze niż użycie INTEGER
. To ma sens, ale czy jest to coś, o co powinienem się martwić w tej skali?
2. Zapisz jako binarne ( bytea
)
W przeciwieństwie do Postgres, MySQL nie ma rodzimego UUID
typu. Istnieje kilka postów wyjaśniających, jak przechowywać UUID za pomocą 16-bajtowego BINARY
pola zamiast 36-bajtowego VARCHAR
. Te posty podsunęły mi pomysł przechowywania klucza jako pliku binarnego ( bytea
na Postgres).
Oszczędza to rozmiar, ale bardziej martwię się wydajnością. Nie miałem szczęścia znaleźć wyjaśnienia, na którym porównanie jest szybsze: binarne lub łańcuchowe. Uważam, że porównania binarne są szybsze. Jeśli tak, bytea
to prawdopodobnie lepiej niż VARCHAR
, mimo że programista musi teraz za każdym razem kodować / dekodować dane.
Mogę się mylić, ale myślę, że oba bytea
i VARCHAR
będą porównywać (równość) bajt po bajcie (lub znak po znaku). Czy istnieje sposób, aby „pominąć” to porównanie krok po kroku i po prostu porównać „całość”? (Nie wydaje mi się, ale sprawdzanie nie kosztuje).
Myślę, że przechowywanie bytea
jest najlepszym rozwiązaniem, ale zastanawiam się, czy są jakieś inne alternatywy, które ignoruję. Również ta sama obawa wyrażona przeze mnie w odniesieniu do rozwiązania 1 jest prawdziwa: czy narzuty związane z porównaniami są wystarczające, że powinienem się martwić?
"Kreatywne rozwiązania
Wymyśliłem dwa bardzo „kreatywne” rozwiązania, które mogłyby zadziałać, po prostu nie jestem pewien, w jakim stopniu (tj. Gdybym miał problem z skalowaniem ich do więcej niż kilku tysięcy wierszy w tabeli).
3. Przechowuj jako, UUID
ale z dołączoną „etykietą”
Głównym powodem nieużywania UUID jest to, że programiści mogą lepiej debugować aplikację. Ale co, jeśli możemy użyć obu: baza danych przechowuje wszystkie klucze UUID
tylko jako s, ale otacza obiekt przed / po zapytaniach.
Na przykład programista pyta ACC-{UUID}
, baza danych ignoruje ACC-
część, pobiera wyniki i zwraca je wszystkie jako {domain}-{UUID}
.
Być może byłoby to możliwe dzięki hakowaniu przy użyciu procedur lub funkcji przechowywanych, ale przychodzą mi na myśl niektóre pytania:
- Czy jest to (usunięcie / dodanie domeny przy każdym zapytaniu) znaczne obciążenie?
- Czy to w ogóle możliwe?
Nigdy wcześniej nie korzystałem z procedur przechowywanych ani funkcji, więc nie jestem pewien, czy jest to w ogóle możliwe. Czy ktoś może rzucić trochę światła? Jeśli mogę dodać przezroczystą warstwę między programatorem a przechowywanymi danymi, wydaje się to idealnym rozwiązaniem.
4. (Mój ulubiony) Zapisz jako IPv6 cidr
Tak, dobrze to przeczytałeś. Okazuje się, że format adresu IPv6 doskonale rozwiązuje mój problem .
- Mogę dodawać domeny i subdomeny w pierwszych kilku oktetach, a pozostałe mogę używać jako ciągi losowe.
- Do kolizji kursy są OK. (Nie użyłbym jednak 2 ^ 128, ale nadal jest OK.)
- Porównania równości są (mam nadzieję) zoptymalizowane, więc mogę uzyskać lepszą wydajność niż zwykłe używanie
bytea
. - Mogę faktycznie wykonać kilka ciekawych porównań, na przykład
contains
w zależności od tego, jak reprezentowane są domeny i ich hierarchia.
Załóżmy na przykład, że używam kodu 0000
do reprezentowania „produktów” w domenie. Klucz 0000:0db8:85a3:0000:0000:8a2e:0370:7334
reprezentowałby produkt 0db8:85a3:0000:0000:8a2e:0370:7334
.
Główne pytanie tutaj: w porównaniu z tym bytea
, czy jest jakaś główna zaleta lub wada korzystania z cidr
typu danych?
varchar
jednym z wielu innych problemów. Nie wiedziałem o domenach pg, o których warto wiedzieć. Widzę domeny używane do sprawdzania poprawności, jeśli dane zapytanie używa poprawnego obiektu, ale nadal opierałoby się na indeksie niecałkowitym. Nie jestem pewien, czy istnieje tutaj „bezpieczny” sposób użycia serial
(bez jednego kroku blokady).
varchar
. Zastanów się, czy jest to FK
integer
typ i dodaj do niego tabelę odnośników. W ten sposób możesz mieć zarówno czytelność dla człowieka, jak i chronić swój kompozyt PK
przed anomaliami wstawiania / aktualizacji (umieszczanie nieistniejącej domeny).
text
jest lepszy niż varchar
. Spójrz na depesz.com/2010/03/02/charx-vs-varcharx-vs-varchar-vs-text i postgresql.org/docs/current/static/datatype-character.html
ACC-f8kJd9xKCd
. ”← To wydaje się być pracą dla starego dobrego kompozytowego KLUCZA PODSTAWOWEGO .