Чи існує штраф за використання BINARY (16) замість UNIQUEIDENTIFIER?


19

Нещодавно я успадкував базу даних SQL Server, яка використовує BINARY(16)замість того, UNIQUEIDENTIFIERщоб зберігати путівники. Це робиться для всього, включаючи первинні ключі.

Чи варто мене турбувати?


Чи він використовує двійкові (16) послідовно протягом усієї? У тому числі для змінних та параметрів? Якщо ні, вам не потрібно враховувати ефекти неявних кастів.
Мартін Сміт

Так, на щастя, мені також не доводиться мати справу з неявними кастами.
Джонатан Аллен

Відповіді:


21

Чи варто мене турбувати?

Ну, тут є декілька речей, які трохи стосуються.

По-перше: хоча це правда, що UNIQUEIDENTIFIER(тобто Guid) є 16-байтовим бінарним значенням, також вірно, що:

  1. Всі дані можуть зберігатися у двійковій формі (наприклад, вони INTможуть зберігатися в BINARY(4), DATETIMEможуть зберігатися в BINARY(8)тощо), отже, №2 ↴
  2. Ймовірно, є причина у наявності окремого типу даних для GUID поза простою зручністю (наприклад, sysnameяк псевдонім для NVARCHAR(128)).

Три поведінкові відмінності, які я можу знайти:

  • Порівняння UNIQUEIDENTIFIERзначень у SQL Server для кращого або гіршого насправді не робиться так само, як порівняння BINARY(16)значень. Відповідно до сторінки MSDN для порівняння значень GUID та унікальних ідентифікаторів при порівнянні UNIQUEIDENTIFIERзначень у SQL Server:

    останні шість байтів значення є найбільш значущими

  • Хоча ці значення часто не сортуються, між цими двома типами є незначна різниця. Відповідно до сторінки MSDN для унікального ідентифікатора :

    упорядкування не реалізується шляхом порівняння бітових моделей двох значень.

  • Зважаючи на те, що існують відмінності в обробці значень GUID між SQL сервером та .NET (зазначено на сторінці "Порівняння значень GUID та унікальних ідентифікаторів", витягнення цих даних із SQL Server у код додатка не може бути належним чином вирішено в код програми, якщо потрібно емулювати поведінку порівняння SQL Server. Таку поведінку можна наслідувати переходом до SqlGuid, але чи знає розробник це робити?

Другий: на основі наступного твердження

Це робиться для всього, включаючи первинні ключі.

Я б взагалі занепокоєний ефективністю системи, використовуючи GUID в якості ПК, а не як альтернативні ключі разом з використанням INTабо навіть BIGINTяк ПК. І ще більше стурбовано, якщо ці GUID ПК - це індекси кластеру.

ОНОВЛЕННЯ

Наступний коментар, зроблений ОП на відповідь @ Роба, викликає додаткову стурбованість:

він був перенесений з, я думаю, MySQL

GUID можуть зберігатися у двох різних бінарних форматах . Отже, може виникнути причина для занепокоєння залежно від:

  1. на якій системі було сформовано бінарне представлення та
  2. якщо рядкові значення використовувались поза початковою системою, наприклад, у коді програми або давались клієнтам для використання у файлах імпорту тощо.

Проблема, де генерується бінарне представлення, пов'язана з упорядкуванням байтів перших 3 з 4 "полів". Якщо ви перейдете за посиланням вище до статті у Вікіпедії, ви побачите, що RFC 4122 вказує використовувати кодування "Big Endian" для всіх 4 полів, проте GUID-адреси Microsoft визначають, використовуючи "Native" Endianness. Ну, архітектура Intel - Little Endian, тому порядок байтів для перших трьох полів змінюється на системах, що слідують за RFC (а також GUID-стилі Microsoft, створені в системах Big Endian). Перше поле "Дані 1" - це 4 байти. В одній Ендіанс це було б представлено як (гіпотетично) 0x01020304. Але в іншому Endianness це було б 0x04030201. Отже, якщо поле поточної бази даних BINARY(16)було заповнене з файлу імпорту, використовуючи двійкові позначення 0x01020304що бінарне представлення було сформовано в системі, що слідує за RFC, а потім перетворення даних, що перебувають у BINARY(16)полі, у UNIQUEIDENTIFIERформат, призведе до іншого GUID, ніж те, що було створено спочатку. Це насправді не створює проблеми, якщо значення ніколи не виходили з бази даних, і значення лише коли-небудь порівнюються за рівність і не впорядкованість.

Проблема із замовленням полягає лише в тому, що після переходу вони не будуть в тому ж порядку UNIQUEIDENTIFIER. На щастя, якщо оригінальною системою справді був MySQL, то впорядкування ніколи не здійснювалося на бінарному представленні, в першу чергу, оскільки MySQL має лише рядкове представлення UUID .

Побоювання з приводу того, що рядкові значення використовуються за межами бази даних, знову серйозніші, якщо двійкове представлення генерується за межами Windows / SQL Server. Оскільки впорядкування байтів потенційно відрізняється, то той самий GUID у рядковій формі призведе до двох різних двійкових уявлень, залежно від того, де відбулося перетворення. Якщо коду додатку або клієнтам було надано GUID у рядковій формі як такий, що ABCвиходить з двійкової форми, 123 а двійкове представлення генерується в системі, що слідує за RFC, то те саме бінарне представлення (тобто 123) перетворюється на рядову форму DEFпри перетворенні в а UNIQUEIDENTIFIER. Аналогічно, початковий рядок у формі ABCперетворюється у двійкову форму 456при перетворенні в a UNIQUEIDENTIFIER.

Отже, якщо GUID ніколи не залишали базу даних, то не варто турбуватися поза замовленням. Або, якщо імпорт з MySQL був здійснений шляхом перетворення рядкової форми (тобто FCCEC3D8-22A0-4C8A-BF35-EC18227C9F40), то це може бути нормально. В іншому випадку, якщо ці GUID були надані клієнтам або в коді програми, ви можете перевірити, як вони конвертують, отримавши один і перетворивши через нього, SELECT CONVERT(UNIQUEIDENTIFIER, 'value found outside of the database');і побачити, чи знайдете ви очікуваний запис. Якщо ви не можете зіставити записи, то, можливо, доведеться зберегти поля як BINARY(16).

Ймовірно, проблеми не буде, але я згадую про це, оскільки при правильних умовах може виникнути проблема.

І як все-таки нові GUID вставляти? Створено в коді програми?

ОНОВЛЕННЯ 2

Якщо попереднє пояснення потенційної проблеми, пов’язаної з імпортом бінарних представлень GUID, сформованих в іншій системі, було трохи (або багато) заплутаним, сподіваємось, наступне буде трохи зрозумілішим:

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

У висновку, показаному вище, значення "String" і "Binary" є з одного GUID. Значення під рядком "Бінарний" - це те саме значення, що і у рядку "Бінарний", але відформатоване у тому ж стилі, що і рядок "Рядок" (тобто видалено "0x" та додано чотири тире). Порівнюючи перше і третє значення, вони не зовсім однакові, але вони дуже близькі: правий - більшість двох розділів однаковий, а лівий - найбільш три розділи - ні. Але якщо придивитися уважно, то можна побачити, що це однакові байти у кожному з трьох розділів, просто в іншому порядку. Можливо, буде легше побачити, якщо я показую лише ці перші три розділи та нумерую байти, щоб було легше побачити, як їх порядок відрізняється між двома представленнями:

Рядок = 1 5F 2 ED 3 23 4 BE - 5 E5 6 2C - 7 40 8 EE
Binary = 4 BE 3 23 2 ED 1 5F - 6 2C 5 E5 - 8 EE 7 40 (у Windows / SQL Server)

Таким чином, у межах кожного групування впорядкування байтів змінюється зворотно, але лише в межах Windows, а також SQL Server. Однак у системі, яка приєднується до RFC, бінарне представлення відображатиме відображення sting, оскільки не було б жодного зміни стороннього порядку.

Як дані були внесені в SQL Server з MySQL? Ось кілька варіантів:

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

Повернення:

0x35464544323342452D453532432D3430  
0x5FED23BEE52C40EE8F4549664C9472FD  
0xBE23ED5F2CE5EE408F4549664C9472FD

Якщо припустити, що це був прямий бінарний у бінарний (тобто конвертувати №2 вище), то отриманий GUID, якщо він перетворюється на фактичний UNIQUEIDENTIFIER, буде таким:

SELECT CONVERT(UNIQUEIDENTIFIER, 0x5FED23BEE52C40EE8F4549664C9472FD);

Повернення:

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

Що неправильно. І це залишає перед нами три питання:

  1. Як було імпортовано дані в SQL Server?
  2. На якій мові написано код програми?
  3. На якій платформі працює код програми?

Я б припустив, що GUID генеруються в додатку, оскільки я не бачу їх у базі даних.
Джонатан Аллен

Я не можу сказати, що я повністю дотримуюся пояснення щодо впорядкування байтів, але це змушує мене думати про індексацію. Чи буде унікальний ідентифікатор більшою чи меншою ймовірністю призвести до фрагментації індексу, ніж двійковий?
Джонатан Аллен

2
@JonathanAllen Я додав ще один розділ ОНОВЛЕННЯ, щоб сподіватись пояснити краще. І ні, індексація не повинна відрізнятися між ними.
Соломон Руцький

"На щастя", SQL Server не змінює впорядкування між Варіантом 1 та Варіантом 2 - навіть якщо "можна" зберігати по-різному на диску, це те саме заплутане замовлення послідовно.
користувач2864740

5

Ви завжди можете бути стурбованими. ;)

Можливо, система була перенесена з іншої системи, яка не підтримує унікальний ідентифікатор. Чи є інші компроміси, про які ви не знаєте?

Дизайнер, можливо, не знав про тип унікального ідентифікатора. Про які ще речі вони не знали?

Технічно - це не повинно викликати особливих проблем.


Так, вона була перенесена з, я думаю, MySQL. І так, є багато ... цікавих речей, які варто подивитися.
Джонатан Аллен
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.