Как я должен хранить GUID в таблицах MySQL?

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

10 ответов


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

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


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


добавляя к ответу 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, упорядоченный цифрами. (Незаконные символы используются в иллюстративных целях - каждое место имеет уникальный символ.) Функции преобразуют порядок байтов в достигните немного порядка для превосходной кластеризации индексов. Переупорядоченный идентификатор guid показан ниже примера.

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

тире удалено:

123456789ABCDEFGHIJKLMNOPQRSTUVW
78563412BC9AFGDEHIJKLMNOPQRSTUVW

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


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

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

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


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


процедура 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, потому что big endian уже дает лучший порядок байтов двоичного индекса. Однако вы хотите, чтобы наиболее значительные байты перемещались перед нижними байтами. Следовательно, 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 кусочках первого блока. Это наименее значимые биты метки времени, что означает, что мы хотим подтолкнуть их вправо, когда мы преобразуем это в индексируемый массив байтов. В качестве примера счетчика последний ID является самым текущим, но алгоритм подкачки KCD поместил бы его перед 3-м ID (3e перед dc, последние байты из первого блока).

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

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

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

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


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

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

Читать статью полностью здесь


Я бы предложил использовать функции ниже, так как те, которые упомянуты @bigh_29, преобразуют мои GUID в новые (по причинам, которые я не понимаю). Кроме того, они немного быстрее в тестах, которые я сделал на своих таблицах. 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 ;

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

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