Czy istnieje kara za użycie BINARY (16) zamiast UNIQUEIDENTIFIER?

19

Niedawno odziedziczyłem bazę danych SQL Server, która używa BINARY(16)zamiast UNIQUEIDENTIFIERprzechowywać Guids. Robi to wszystko, w tym klucze podstawowe.

Czy powinienem się martwić?

Jonathan Allen
źródło
Czy używa binarnie (16) konsekwentnie przez cały czas? W tym dla zmiennych i parametrów? Jeśli nie, musisz rozważyć skutki niejawnych rzutów.
Martin Smith
Tak, na szczęście nie mam też do czynienia z niejawnymi obsadami.
Jonathan Allen

Odpowiedzi:

21

Czy powinienem się martwić?

Jest tu kilka rzeczy, które są trochę niepokojące.

Po pierwsze: chociaż prawdą jest, że a UNIQUEIDENTIFIER(tj. Guid) Jest 16-bajtową wartością binarną, prawdą jest również, że:

  1. Wszystkie dane mogą być przechowywane w formie binarnej (np. INTMogą być przechowywane BINARY(4), DATETIMEmogą być przechowywane w BINARY(8)itp.), Stąd # 2 ↴
  2. Prawdopodobnie istnieje powód posiadania osobnego typu danych dla identyfikatorów GUID poza zwykłą wygodą (np. sysnameJako alias NVARCHAR(128)).

Trzy różnice behawioralne, które mogę znaleźć to:

  • Porównywanie UNIQUEIDENTIFIERwartości w SQL Server, na lepsze lub na gorsze, w rzeczywistości nie odbywa się w ten sam sposób, co porównywanie BINARY(16)wartości. Zgodnie ze stroną MSDN do porównywania wartości identyfikatora GUID i unikalnego identyfikatora podczas porównywania UNIQUEIDENTIFIERwartości w programie SQL Server:

    ostatnie sześć bajtów wartości jest najbardziej znaczących

  • Chociaż te wartości nie są często sortowane, istnieje niewielka różnica między tymi dwoma typami. Według strony MSDN dla unikalnego identyfikatora :

    porządkowanie nie jest realizowane przez porównywanie wzorów bitowych dwóch wartości.

  • Biorąc pod uwagę, że istnieją różnice w sposobie, w jaki wartości GUID są obsługiwane między SQL Server i .NET (zauważono na stronie „Porównywanie wartości GUID i unikatowych identyfikatorów” powyżej), wyciągnięcie tych danych z SQL Server do kodu aplikacji może nie być właściwie obsługiwane w kod aplikacji, jeśli trzeba emulować zachowanie porównawcze programu SQL Server. To zachowanie można emulować, konwertując na a SqlGuid, ale czy programista wiedziałby o tym?

Po drugie: w oparciu o następujące oświadczenie

Robi to wszystko, w tym klucze podstawowe.

Byłbym ogólnie zaniepokojony wydajnością systemu za pomocą GUID-ów jako PK zamiast kluczy alternatywnych wraz z używaniem INTlub nawet BIGINTjako PK. I jeszcze bardziej zaniepokojony, jeśli te GUID PK są indeksami klastrowymi.

AKTUALIZACJA

Poniższy komentarz, zamieszczony przez OP w odpowiedzi na @ Roba, budzi dodatkowe obawy:

został migrowany z MySQL

Identyfikatory GUID mogą być przechowywane w 2 różnych formatach binarnych . Tak więc, nie mógłby być powodem do niepokoju w zależności od:

  1. w jakim systemie została wygenerowana reprezentacja binarna, oraz
  2. jeśli wartości ciągów zostały użyte poza oryginalnym systemem, na przykład w kodzie aplikacji lub przekazane klientom do użycia w plikach importu itp.

Problem z wygenerowaniem reprezentacji binarnej dotyczy uporządkowania bajtów pierwszych 3 z 4 „pól”. Jeśli klikniesz powyższy link do artykułu w Wikipedii, zobaczysz, że RFC 4122 określa użycie kodowania „Big Endian” dla wszystkich 4 pól, ale identyfikatory GUID firmy Microsoft określają użycie „Native” Endianness. Architektura Intela to Little Endian, stąd kolejność bajtów dla pierwszych 3 pól jest odwrócona od systemów zgodnych z RFC (podobnie jak identyfikatory GUID typu Microsoft generowane w systemach Big Endian). Pierwsze pole, „Dane 1”, ma 4 bajty. W jednym Endianness byłby reprezentowany jako (hipotetycznie) 0x01020304. Ale w drugiej Endianness byłoby to 0x04030201. Więc jeśli bieżąca baza danych „BINARY(16)ta reprezentacja binarna została wygenerowana w systemie zgodnym z RFC, a następnie konwersja danych znajdujących się obecnie w BINARY(16)polu na inny UNIQUEIDENTIFIERspowoduje, że GUID będzie inny niż pierwotnie utworzony. Nie stanowi to tak naprawdę problemu, JEŻELI wartości nigdy nie opuściły bazy danych, a wartości są zawsze porównywane tylko dla równości, a nie porządku.

Problem z zamawianiem polega po prostu na tym, że po konwersji nie będą one w tej samej kolejności UNIQUEIDENTIFIER. Na szczęście, jeśli oryginalnym systemem naprawdę był MySQL, to w ogóle nigdy nie przeprowadzono porządkowania reprezentacji binarnej, ponieważ MySQL ma tylko ciąg znaków UUID .

Niepokój związany z używaniem wartości ciągu poza bazą danych jest poważniejszy, jeśli reprezentacja binarna została wygenerowana poza systemem Windows / SQL Server. Ponieważ kolejność bajtów jest potencjalnie różna, ten sam identyfikator GUID w postaci ciągu spowodowałby 2 różne reprezentacje binarne, w zależności od tego, gdzie miała miejsce ta konwersja. Jeśli kod aplikacji lub klienci otrzymają identyfikator GUID w postaci ciągu jako ABCpochodzący z postaci binarnej, 123 a reprezentacja binarna zostanie wygenerowana w systemie zgodnym z RFC, wówczas ta sama reprezentacja binarna (tj. 123) Przełoży się na postać ciągową DEFpo przekonwertowaniu na UNIQUEIDENTIFIER. Podobnie, oryginalna forma ciągu ABCprzekształciłaby się w postać binarną 456po konwersji na a UNIQUEIDENTIFIER.

Jeśli więc identyfikatory GUID nigdy nie opuściły bazy danych, nie ma powodów do obaw poza zamówieniem. Lub, jeśli import z MySQL został dokonany przez konwersję postaci ciągu (tj. FCCEC3D8-22A0-4C8A-BF35-EC18227C9F40), Może być w porządku. W przeciwnym razie, jeśli te identyfikatory GUID zostały podane klientom lub w kodzie aplikacji, możesz przetestować, aby przekonać się, w jaki sposób dokonują konwersji, uzyskując jeden i konwertując je, SELECT CONVERT(UNIQUEIDENTIFIER, 'value found outside of the database');i sprawdzając, czy znajdziesz oczekiwany rekord. Jeśli nie możesz dopasować rekordów, być może będziesz musiał zachować pola jako BINARY(16).

Najprawdopodobniej nie będzie problemu, ale wspominam o tym, ponieważ w odpowiednich warunkach może wystąpić problem.

A w jaki sposób wstawiane są nowe identyfikatory GUID? Wygenerowano w kodzie aplikacji?

AKTUALIZACJA 2

Jeśli poprzednie wyjaśnienie potencjalnego problemu związanego z importowaniem reprezentacji binarnych identyfikatorów GUID wygenerowanych w innym systemie było nieco (lub bardzo) mylące, mam nadzieję, że następujące wyjaśnienia będą nieco jaśniejsze:

DECLARE @GUID UNIQUEIDENTIFIER = NEWID();
SELECT @GUID AS [String], CONVERT(BINARY(16), @GUID) AS [Binary];
-- String = 5FED23BE-E52C-40EE-8F45-49664C9472FD
-- Binary = 0xBE23ED5F2CE5EE408F4549664C9472FD
--          BE23ED5F-2CE5-EE40-8F45-49664C9472FD

W wynikach pokazanych powyżej wartości „String” i „Binary” pochodzą z tego samego identyfikatora GUID. Wartość pod wierszem „Binarna” jest taka sama jak linia „Binarna”, ale sformatowana w tym samym stylu co linia „Ciąg” (tzn. Usunięta „0x” i dodała cztery myślniki). Porównując pierwszą i trzecią wartość, nie są dokładnie takie same, ale są bardzo blisko: dwie skrajnie prawe dwie sekcje są identyczne, ale trzy skrajnie lewe nie są. Ale jeśli przyjrzysz się uważnie, zobaczysz, że są to te same bajty w każdej z trzech sekcji, tylko w innej kolejności. Łatwiej byłoby zobaczyć, czy pokazuję tylko te trzy pierwsze sekcje i numeruję bajty, więc łatwiej jest zobaczyć, jak różni się ich kolejność między dwiema reprezentacjami:

Łańcuch = 1 5F 2 ED 3 23 4 BE - 5 E5 6 2C - 7 40 8 EE
Binarny = 4 BE 3 23 2 ED 1 5F - 6 2C 5 E5 - 8 EE 7 40 (w systemie Windows / SQL Server)

Tak więc w każdej grupie kolejność bajtów jest odwrócona, ale tylko w systemie Windows, a także SQL Server. Jednak w systemie zgodnym z RFC reprezentacja binarna odzwierciedlałaby reprezentację żądła, ponieważ nie nastąpiłoby odwrócenie kolejności bajtów.

W jaki sposób dane zostały wprowadzone do SQL Server z MySQL? Oto kilka opcji:

SELECT CONVERT(BINARY(16), '5FED23BE-E52C-40EE-8F45-49664C9472FD'),
       CONVERT(BINARY(16), 0x5FED23BEE52C40EE8F4549664C9472FD),
    CONVERT(BINARY(16), CONVERT(UNIQUEIDENTIFIER, '5FED23BE-E52C-40EE-8F45-49664C9472FD'));

Zwroty:

0x35464544323342452D453532432D3430  
0x5FED23BEE52C40EE8F4549664C9472FD  
0xBE23ED5F2CE5EE408F4549664C9472FD

Zakładając, że był to prosty układ dwójkowy na dwójkowy (tj. Konwertuj nr 2 powyżej), wynikowy identyfikator GUID, jeśli zostanie przekonwertowany na rzeczywisty UNIQUEIDENTIFIER, będzie:

SELECT CONVERT(UNIQUEIDENTIFIER, 0x5FED23BEE52C40EE8F4549664C9472FD);

Zwroty:

BE23ED5F-2CE5-EE40-8F45-49664C9472FD

Co jest złe. I to pozostawia nam trzy pytania:

  1. W jaki sposób dane zostały zaimportowane do SQL Server?
  2. W jakim języku jest napisany kod aplikacji?
  3. Na jakiej platformie działa kod aplikacji?
Solomon Rutzky
źródło
Zakładam, że identyfikatory GUID są generowane w aplikacji, ponieważ nie widzę ich w bazie danych.
Jonathan Allen
Nie mogę powiedzieć, że całkowicie przestrzegam objaśnień dotyczących porządkowania bajtów, ale powoduje, że myślę o indeksowaniu. Czy unikalny identyfikator byłby bardziej lub mniej skłonny do fragmentacji indeksu niż binarny?
Jonathan Allen
2
@JonathanAllen Dodałem kolejną sekcję AKTUALIZACJI, aby, mam nadzieję, lepiej wyjaśnić. I nie, indeksowanie nie powinno się między nimi różnić.
Solomon Rutzky
„Na szczęście” SQL Server nie zmienia kolejności między wariantem 1 i wariantem 2 - nawet jeśli „można” przechowywać inaczej na dysku, to samo mylące porządkowanie konsekwentnie.
user2864740,
5

Zawsze możesz się martwić. ;)

System mógł zostać migrowany z innego systemu, który nie obsługuje unikalnego identyfikatora. Czy są jeszcze inne kompromisy, o których nie wiesz?

Projektant mógł nie wiedzieć o typie unikalnego identyfikatora. O jakich innych sprawach nie wiedzieli?

Technicznie jednak - nie powinno to stanowić większego problemu.

Rob Farley
źródło
Tak, został migrowany z MySQL. I tak, jest wiele ... interesujących rzeczy do obejrzenia.
Jonathan Allen,