Как мне хранить GUID в таблицах MySQL?

146

Я использую varchar (36) или есть ли лучшие способы сделать это?

CDR
источник
1
"thaBadDawg" предлагает хороший ответ. Существует параллельная тема о переполнении стека, которая обсуждает эту тему. Я добавил несколько комментариев к темам, отвечая на эту ссылку на ресурсы более подробно. Вот ссылка на вопрос: stackoverflow.com/questions/547118/storing-mysql-guid-uuids - я ожидаю, что эта тема станет более распространенной, когда люди начнут рассматривать AWS и Aurora.
Зак Яннсен

Ответы:

104

Мой администратор базы данных спросил меня, когда я спросил о наилучшем способе хранения GUID для моих объектов, почему мне нужно было хранить 16 байтов, когда я мог сделать то же самое в 4 байта с целым числом. Поскольку он поставил мне этот вызов, я подумал, что сейчас самое время упомянуть об этом. Что, как говорится...

Вы можете хранить guid как двоичный файл CHAR (16), если хотите наиболее оптимально использовать пространство для хранения.

thaBadDawg
источник
176
Потому что с 16 байтами вы можете генерировать вещи в разных базах данных, на разных машинах, в разное время и при этом без проблем объединять данные вместе :)
Billy ONeal
4
нужен ответ, что на самом деле является двоичным символом char 16? не чар? не бинарный? Я не вижу такого типа ни в одном из инструментов mysql gui, ни в документации на сайте mysql. @BillyONeal
nawfal
3
@nawfal: Char - это тип данных. BINARY - это спецификатор типа для типа. Единственный эффект, который он имеет, - это изменение порядка сортировки в MySQL. См. Dev.mysql.com/doc/refman/5.0/en/charset-binary-op.html для получения более подробной информации. Конечно, вы можете просто использовать тип BINARY напрямую, если ваш инструмент редактирования базы данных позволяет вам это делать. (Старые инструменты не знают о типе двоичных данных, но знают о флаге двоичного столбца)
Billy ONeal
2
поле CHAR и BINARY - это одно и то же. Если вы хотите перейти на самый базовый из уровней, CHAR - это двоичное поле, ожидающее значение от 0 до 255 с целью представления указанного значения значением, отображенным из справочной таблицы (в большинстве случаев сейчас UTF8). Поле BINARY ожидает тот же тип значения без какого-либо намерения представлять упомянутые данные из справочной таблицы. Я использовал CHAR (16) в течение дней 4.x, потому что тогда MySQL был не так хорош, как сейчас.
thaBadDawg
15
Есть несколько веских причин, по которым GUID намного лучше, чем автоинкремент. Джефф Этвуд перечисляет эти . Для меня лучшим преимуществом использования GUID является то, что моему приложению не понадобится обратное обращение к базе данных, чтобы узнать ключ сущности: я мог бы заполнить его программно, что я не смог бы сделать, если бы использовал поле автоинкремента. Это избавило меня от нескольких головных болей: с GUID я могу управлять сущностью одинаково, независимо от того, сущность уже сохранена или она совершенно новая.
Ариальдо Мартини
48

Я бы сохранил его как символ (36).

Брайан Фишер
источник
5
Я не могу понять, почему вы должны хранить -с.
Афшин Мехрабани
2
@AfshinMehrabani Это просто, понятно и понятно для человека. Конечно, в этом нет необходимости, но если сохранение этих дополнительных байтов не повредит, то это лучшее решение.
user1717828
2
Хранение тире может быть не очень хорошей идеей, потому что это приведет к увеличению накладных расходов. Если вы хотите сделать его читаемым человеком, сделайте приложение читаемым с тире.
Лукка Ферри
@AfshinMehrabani еще одно соображение заключается в анализе его из базы данных. Большинство реализаций ожидают тире в действительном guid.
Райан Гейтс
Вы можете вставить дефис при извлечении, чтобы легко преобразовать символ (32) в символ (36). используйте Вставить FN mySql.
joedotnot
33

В дополнение к ответу ThaBadDawg, используйте эти удобные функции (благодаря моему более мудрому коллеге), чтобы получить строку длиной 36 обратно в массив байтов из 16.

DELIMITER $$

CREATE FUNCTION `GuidToBinary`(
    $Data VARCHAR(36)
) RETURNS binary(16)
DETERMINISTIC
NO SQL
BEGIN
    DECLARE $Result BINARY(16) DEFAULT NULL;
    IF $Data IS NOT NULL THEN
        SET $Data = REPLACE($Data,'-','');
        SET $Result =
            CONCAT( UNHEX(SUBSTRING($Data,7,2)), UNHEX(SUBSTRING($Data,5,2)),
                    UNHEX(SUBSTRING($Data,3,2)), UNHEX(SUBSTRING($Data,1,2)),
                    UNHEX(SUBSTRING($Data,11,2)),UNHEX(SUBSTRING($Data,9,2)),
                    UNHEX(SUBSTRING($Data,15,2)),UNHEX(SUBSTRING($Data,13,2)),
                    UNHEX(SUBSTRING($Data,17,16)));
    END IF;
    RETURN $Result;
END

$$

CREATE FUNCTION `ToGuid`(
    $Data BINARY(16)
) RETURNS char(36) CHARSET utf8
DETERMINISTIC
NO SQL
BEGIN
    DECLARE $Result CHAR(36) DEFAULT NULL;
    IF $Data IS NOT NULL THEN
        SET $Result =
            CONCAT(
                HEX(SUBSTRING($Data,4,1)), HEX(SUBSTRING($Data,3,1)),
                HEX(SUBSTRING($Data,2,1)), HEX(SUBSTRING($Data,1,1)), '-', 
                HEX(SUBSTRING($Data,6,1)), HEX(SUBSTRING($Data,5,1)), '-',
                HEX(SUBSTRING($Data,8,1)), HEX(SUBSTRING($Data,7,1)), '-',
                HEX(SUBSTRING($Data,9,2)), '-', HEX(SUBSTRING($Data,11,6)));
    END IF;
    RETURN $Result;
END
$$

CHAR(16)на самом деле BINARY(16), выберите предпочтительный вкус

Чтобы лучше следовать коду, возьмите пример с приведенным ниже порядковым номером GUID. (Недопустимые символы используются в иллюстративных целях - каждый из них содержит уникальный символ.) Функции преобразуют порядок байтов для достижения порядка битов для превосходной кластеризации индекса. Переупорядоченная направляющая показана под примером.

12345678-9ABC-DEFG-HIJK-LMNOPQRSTUVW
78563412-BC9A-FGDE-HIJK-LMNOPQRSTUVW

Черточки удалены:

123456789ABCDEFGHIJKLMNOPQRSTUVW
78563412BC9AFGDEHIJKLMNOPQRSTUVW
Строймеханизация
источник
Вот приведенный выше GuidToBinary без удаления дефисов из строки: CREATE FUNCTION GuidToBinary($ guid char (36)) RETURNS binary (16) RETURN CONCAT (UNHEX (SUBSTRING ($ guid, 7, 2))), UNHEX (SUBSTRING ($ guid, 5, 2)), UNHEX (SUBSTRING ($ guid, 3, 2)), UNHEX (SUBSTRING ($ guid, 1, 2)), UNHEX (SUBSTRING ($ guid, 12, 2)), UNHEX (SUBSTRING ($ guid, 10, 2)), UNHEX (SUBSTRING ($ guid, 17, 2)), UNHEX (SUBSTRING ($ guid, 15, 2)), UNHEX (SUBSTRING ($ guid, 20, 4))), UNHEX (SUBSTRING ($ guid, 25, 12)));
Джонатан Оливер
4
Для любопытных, эти функции превосходят просто UNHEX (REPLACE (UUID (), '-', '')), потому что он размещает биты в порядке, который будет работать лучше в кластеризованном индексе.
Slashterix
Это очень полезно, но я чувствую, что его можно улучшить с помощью источника CHARи BINARYэквивалентности ( документы, по- видимому, подразумевают, что существуют важные различия и объяснение того, почему производительность кластерного индекса лучше с переупорядоченными байтами.
Патрик М.
Когда я использую это, мой гид изменяется. Я пытался вставить его, используя как unhex (replace (string, '-', '')), так и функцию выше, и когда я конвертирую их обратно теми же методами, выбранный guid не тот, который был вставлен. Что превращает гид? Все, что я сделал, это скопировал код сверху.
vsdev
@JonathanOliver Не могли бы вы поделиться кодом для функции BinaryToGuid ()?
Арун Аванатан,
27

char (36) будет хорошим выбором. Также можно использовать функцию MySQL UUID (), которая возвращает 36-символьный текстовый формат (шестнадцатеричный с дефисами), который можно использовать для извлечения таких идентификаторов из БД.

Обучение
источник
19

«Лучше» зависит от того, для чего вы оптимизируете.

Насколько вы заботитесь о размере / производительности хранилища в сравнении с простотой разработки? Что еще более важно - вы генерируете достаточно GUID или загружаете их достаточно часто, чтобы это имело значение?

Если ответ «нет», char(36)это более чем достаточно, и это делает хранение / выборку GUID очень простым. В противном случае binary(16)это разумно, но вам придется опираться на MySQL и / или на предпочитаемый вами язык программирования, чтобы конвертировать туда и обратно из обычного строкового представления.

CANDU
источник
2
Если вы размещаете программное обеспечение (например, веб-страницу) и не продаете / не устанавливаете в клиенте, вы всегда можете начать с char (36), чтобы упростить разработку на ранней стадии, и перейти к более компактному формат по мере роста использования системы и начала нуждаться в оптимизации.
Хави Монтеро
1
Самым большим недостатком гораздо большего символа (36) является то, сколько места займет индекс. Если у вас есть большое количество записей в базе данных, вы удваиваете размер индекса.
bpeikes
8

Двоичный (16) будет хорошо, лучше, чем использование varchar (32).

Онкар Джанва
источник
7

Подпрограмма GuidToBinary, опубликованная KCD, должна быть настроена так, чтобы учитывать расположение битов временной метки в строке GUID. Если строка представляет UUID версии 1, как те, которые возвращаются подпрограммой mysql uuid (), то временные компоненты включаются в буквы 1-G, исключая D.

12345678-9ABC-DEFG-HIJK-LMNOPQRSTUVW
12345678 = least significant 4 bytes of the timestamp in big endian order
9ABC     = middle 2 timestamp bytes in big endian
D        = 1 to signify a version 1 UUID
EFG      = most significant 12 bits of the timestamp in big endian

Когда вы преобразуете в двоичный файл, лучшим порядком для индексации будет: EFG9ABC12345678D + остальное.

Вы не хотите поменять местами 12345678 на 78563412, потому что больший порядок байтов уже дает лучший порядок байтов двоичного индекса. Однако вы хотите, чтобы наиболее значимые байты были перемещены перед младшими байтами. Следовательно, EFG идет первым, затем идут средние и младшие биты. Создайте дюжину UUID с помощью uuid () в течение минуты, и вы должны увидеть, как этот порядок дает правильный ранг.

select uuid(), 0
union 
select uuid(), sleep(.001)
union 
select uuid(), sleep(.010)
union 
select uuid(), sleep(.100)
union 
select uuid(), sleep(1)
union 
select uuid(), sleep(10)
union
select uuid(), 0;

/* output */
6eec5eb6-9755-11e4-b981-feb7b39d48d6
6eec5f10-9755-11e4-b981-feb7b39d48d6
6eec8ddc-9755-11e4-b981-feb7b39d48d6
6eee30d0-9755-11e4-b981-feb7b39d48d6
6efda038-9755-11e4-b981-feb7b39d48d6
6f9641bf-9755-11e4-b981-feb7b39d48d6
758c3e3e-9755-11e4-b981-feb7b39d48d6 

Первые два UUID были сгенерированы ближе всего по времени. Они различаются только в последних 3 полубайтах первого блока. Это наименее значимые биты метки времени, что означает, что мы хотим сдвинуть их вправо, когда преобразуем это в индексируемый байтовый массив. В качестве встречного примера последний идентификатор является самым текущим, но алгоритм обмена KCD поместил бы его перед третьим идентификатором (3e перед dc, последние байты из первого блока).

Правильный порядок для индексации будет:

1e497556eec5eb6... 
1e497556eec5f10... 
1e497556eec8ddc... 
1e497556eee30d0... 
1e497556efda038... 
1e497556f9641bf... 
1e49755758c3e3e... 

См. Эту статью для поддержки информации: http://mysql.rjweb.org/doc.php/uuid

*** обратите внимание, что я не разделяю ниппель версии с старшими 12 битами метки времени. Это D клев от вашего примера. Я просто бросаю это впереди. Так что моя двоичная последовательность заканчивается тем, что DEFG9ABC и так далее. Это означает, что все мои проиндексированные UUID начинаются с одного и того же куска. Статья делает то же самое.

bigh_29
источник
цель этого для экономии места для хранения? или сделать сортировку их полезной?
MD004
1
@ MD004. Это создает лучший индекс сортировки. Пространство остается прежним.
bigh_29
5

Для тех, кто только что наткнулся на это, теперь есть гораздо лучшая альтернатива, согласно исследованию Percona.

Он состоит из реорганизации блоков UUID для оптимальной индексации, а затем преобразования в двоичный файл для сокращения объема хранения.

Прочитайте полную статью здесь

sleepycal
источник
Я читал эту статью раньше. Я нахожу это очень интересным, но тогда как мы должны выполнить запрос, если мы хотим фильтровать по идентификатору, который является двоичным? Я думаю, нам нужно снова проклясть, а затем применить критерии. Это так требовательно? Зачем хранить двоичный файл (16) (уверен, что он лучше, чем varchar (36)) вместо bigint из 8 байт?
Максимус Децимус
2
Есть обновленная статья от MariaDB, которая должна ответить на ваш вопрос mariadb.com/kb/en/mariadb/guiduuid-performance
sleepycal
Кстати, UUIDv4 полностью случайный и не требует чанкинга.
Махмуд Аль-Кудси
2

Я бы предложил использовать функции, указанные ниже, так как те, что упомянуты @ bigh_29, преобразуют мои направляющие в новые (по причинам, которые я не понимаю). Кроме того, они немного быстрее в тех тестах, которые я проводил на своих столах. https://gist.github.com/damienb/159151

DELIMITER |

CREATE FUNCTION uuid_from_bin(b BINARY(16))
RETURNS CHAR(36) DETERMINISTIC
BEGIN
  DECLARE hex CHAR(32);
  SET hex = HEX(b);
  RETURN LOWER(CONCAT(LEFT(hex, 8), '-', MID(hex, 9,4), '-', MID(hex, 13,4), '-', MID(hex, 17,4), '-', RIGHT(hex, 12)));
END
|

CREATE FUNCTION uuid_to_bin(s CHAR(36))
RETURNS BINARY(16) DETERMINISTIC
RETURN UNHEX(CONCAT(LEFT(s, 8), MID(s, 10, 4), MID(s, 15, 4), MID(s, 20, 4), RIGHT(s, 12)))
|

DELIMITER ;
vsdev
источник
-4

если у вас есть значение char / varchar, отформатированное как стандартный GUID, вы можете просто сохранить его как BINARY (16), используя простой CAST (MyString AS BINARY16), без всех этих ошеломляющих последовательностей CONCAT + SUBSTR.

BINARY (16) поля сравниваются / сортируются / индексируются намного быстрее, чем строки, а также занимают в два раза меньше места в базе данных

Джордж Хазан
источник
2
Выполнение этого запроса показывает, что CAST преобразует строку uuid в байты ASCII: set @a = uuid (); выберите @a, hex (cast (@a AS BINARY (16))); Я получаю 16f20d98-9760-11e4-b981-feb7b39d48d6: 3136663230643938 2D 39373630 2D 3131 (добавлены пробелы для форматирования). 0x31 = ascii 1, 0x36 = ascii 6. Мы даже получаем 0x2D, ​​который является дефисом. Это мало чем отличается от простого сохранения guid в виде строки, за исключением того, что вы усекаете строку до 16-го символа, что приводит к удалению части идентификатора, относящейся к конкретной машине.
bigh_29
Да, это просто усечение. select CAST("hello world, this is as long as uiid" AS BINARY(16));производитhello world, thi
MD004