Есть ли штраф за использование BINARY (16) вместо UNIQUEIDENTIFIER?

19

Я недавно унаследовал базу данных SQL Server, которая использует BINARY(16)вместо UNIQUEIDENTIFIERхранения Guids. Это делает это для всего, включая первичные ключи.

Должен ли я быть обеспокоен?

Джонатан Аллен
источник
Использует ли он двоичный код (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 Server и .NET (отмечено на странице «Сравнение значений GUID и уникальных идентификаторов», ссылка на которую приведена выше), извлечение этих данных из SQL Server в код приложения может быть неправильно обработано в код приложения, если необходимо эмулировать поведение сравнения SQL Server. Такое поведение можно эмулировать, конвертируя в a SqlGuid, но знает ли разработчик, что делать это?

Второе: на основании следующего утверждения

Это делает это для всего, включая первичные ключи.

В целом, я бы беспокоился о производительности системы, используя GUID в качестве PK вместо альтернативных ключей вместе с использованием INTили даже BIGINTв качестве PK. И даже больше беспокоит, являются ли эти GUID PK кластерными индексами.

ОБНОВИТЬ

Следующий комментарий, сделанный ФП к ответу @ Роба, вызывает дополнительную озабоченность:

это было перенесено с, я думаю, MySQL

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

  1. на какой системе было сгенерировано двоичное представление, и
  2. если строковые значения использовались за пределами исходной системы, например, в коде приложения или передавались клиентам для использования в файлах импорта и т. д.

Проблема с тем, где было сгенерировано двоичное представление, связана с порядком байтов первых 3 из 4 «полей». Если вы перейдете по ссылке выше на статью в Википедии, вы увидите, что RFC 4122 указывает на использование кодировки «Big Endian» для всех 4 полей, а идентификаторы Microsoft GUID указывают на использование «Собственного» Endianness. Итак, архитектура Intel имеет формат Little Endian, следовательно, порядок следования байтов для первых 3 полей меняется в зависимости от RFC (а также GUID в стиле Microsoft, генерируемых в системах Big Endian). Первое поле, «Данные 1», составляет 4 байта. В одном Endianness это будет представлено как (гипотетически) 0x01020304. Но в другом Endianness это было бы 0x04030201. Так что, если текущая база данныхBINARY(16)это двоичное представление было сгенерировано в системе, следующей за RFC, и преобразование данных, находящихся в настоящее время в BINARY(16)поле, в UNIQUEIDENTIFIERрезультат приведет к тому, что GUID будет отличаться от того, который был изначально создан. Это на самом деле не создает проблемы, если значения никогда не покидают базу данных, а значения сравниваются только на равенство, а не на порядок.

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

Опасность использования строковых значений вне базы данных более серьезна, опять же, если двоичное представление было создано вне Windows / SQL Server. Поскольку порядок байтов потенциально различен, один и тот же GUID в строковой форме приведет к 2 различным двоичным представлениям, в зависимости от того, где произошло это преобразование. Если код приложения или клиенты получили GUID в виде строки как ABCисходящий из двоичной формы 123 и двоичное представление было сгенерировано в системе, следующей за RFC, то это же двоичное представление (то есть 123) будет преобразовано в строковую форму DEFпри преобразовании в а UNIQUEIDENTIFIER. Аналогично, исходная строковая форма ABCпреобразуется в двоичную форму 456при преобразовании в 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. Значение под строкой «Binary» совпадает со значением строки «Binary», но отформатировано в том же стиле, что и строка «String» (т. Е. Удалено «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, двоичное представление будет зеркально отображать строковое представление, потому что не будет никакого изменения порядка байтов.

Как данные были перенесены в 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 генерируются в приложении, так как я не вижу их в базе данных.
Джонатан Аллен
Не могу сказать, что полностью следую объяснениям порядка следования байтов, но это заставляет меня задуматься об индексации. Будет ли uniqueidentifier более или менее вероятно привести к фрагментации индекса, чем двоичный файл?
Джонатан Аллен
2
@JonathanAllen Я добавил еще один раздел ОБНОВЛЕНИЕ, чтобы, надеюсь, объяснить лучше. И нет, между ними индексирование не должно быть разным.
Соломон Руцкий
«К счастью», SQL Server не меняет порядок между Вариантом 1 и Вариантом 2 - даже если «может» по-разному хранится на диске, это одинаково сбивает с толку упорядоченность.
user2864740
5

Вы всегда можете быть обеспокоены. ;)

Возможно, система была перенесена из какой-то другой системы, которая не поддерживает uniqueidentifier. Есть ли другие компромиссы, о которых вы не знаете?

Разработчик может не знать о типе uniqueidentifier. О чем еще они не знали?

Технически, хотя - это не должно быть серьезной проблемой.

Роб Фарли
источник
Да, это было перенесено с, я думаю, MySQL. И да, есть много ... интересных вещей, на которые можно посмотреть.
Джонатан Аллен