Як слід зберігати GUID у таблицях MySQL?


146

Чи використовую я varchar (36) чи є кращі способи це зробити?


1
"thaBadDawg" пропонує хорошу відповідь. На Stack Overflow є паралельна нитка, яка обговорює тему. Я додав кілька коментарів до цих тем, які відповідають на це посилання на ресурси більш детально. Ось посилання на запитання: stackoverflow.com/questions/547118/storing-mysql-guid-uuids - Я очікую, що ця тема стане більш поширеною, коли люди почнуть розглядати AWS та Aurora.
Zack Jannsen

Відповіді:


104

Мій DBA запитав мене, коли я запитував про найкращий спосіб зберігання GUID для моїх об’єктів, чому мені потрібно зберігати 16 байт, коли я можу зробити те саме в 4 байтах із цілим числом. Оскільки він поставив мені це завдання, я подумав, що зараз вдалий час згадати про це. Це сумно...

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


176
Тому що, використовуючи 16 байт, ви можете генерувати речі в різних базах даних, на різних машинах, в різний час, і все одно безперешкодно об’єднувати дані :)
Billy ONeal

4
потрібна відповідь, що насправді є двійковим символом 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) ще за 4x дні, тому що MySQL тоді не був таким хорошим, як зараз.
thaBadDawg

15
Є кілька вагомих причин, чому GUID набагато кращий, ніж автопосилення. Джефф Етвуд перераховує це . Для мене найкращою перевагою використання GUID є те, що моєму додатку не знадобиться зворотний набір баз даних, щоб знати ключ об'єкта: я міг би його заповнити програмно, що я не міг би зробити, якщо використовував поле автоматичного збільшення. Це врятувало мене від декількох головних болів: за допомогою GUID я можу керувати сутністю таким же чином, незалежно від того, сутність вже зберігається або це абсолютно нове.
Аріальдо Мартіні

48

Я б зберігав це як чар (36).


5
Я не бачу, чому ви повинні зберігати -s.
Афшин Мехрабані

2
@AfshinMehrabani Це просто, просто, зрозуміло людині. Звичайно, це не обов'язково, але якщо зберігання цих зайвих байтів не зашкодить, то це найкраще рішення.
користувач1717828

2
Зберігання дефісів може бути не дуже хорошою ідеєю, оскільки це призведе до великих витрат. Якщо ви хочете зробити його зрозумілим для людини, зробіть додаток читанням з тире.
Лукка Феррі

@AfshinMehrabani черговий розгляд - це аналіз його з бази даних. Більшість реалізацій очікують тире в дійсній інструкції.
Райан Гейтс

Ви можете вставляти дефіси під час отримання, щоб легко перетворити знак char (32) у char (36). використовувати Insert 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 без вилучення дефісів з рядка: СТВОРИТИ ФУНКЦІЮ GuidToBinary($ GUI char (36)) ВІДПОВІДАЄТЬСЯ бінарний (16) ПОВЕРНЕННЯ CONCAT (UNHEX (SUBSTRING ($ Guid, 7, 2)), UNHEX (SUBSTRING ($ guide, 5, 2)), UNHEX (SUBSTRING ($ guide, 3, 2)), UNHEX (SUBSTRING ($ guide, 1, 2)), UNHEX (SUBSTRING ($ guide, 12, 2)), UNHEX (SUBSTRING ($ guide, 10, 2)), UNHEX (SUBSTRING ($ Guid, 17, 2)), UNHEX (SUBSTRING ($ guide, 15, 2)), UNHEX (SUBSTRING ($ Guid, 20, 4)), UNHEX (ПІДПРИЄМСТВО) ($ Guid, 25, 12)));
Джонатан Олівер

4
Для допитливих ці функції перевершують саме UNHEX (ЗАМОВИТИ (UUID (), '-', '')), оскільки він упорядковує біти в порядку, який буде краще працювати в кластерному індексі.
Slashterix

Це дуже корисно, але я вважаю, що це може бути покращено за допомогою джерела для CHARта BINARYеквівалентності ( документи, мабуть, означають, що існують важливі відмінності та пояснення того, чому ефективність кластеризованого індексу краща з перепорядкованими байтами.
Патрік М,

Коли я користуюсь цим, моя настанова змінюється. Я намагався вставити його, використовуючи як unhex (заміну (рядок, '-', '')), так і вищевказану функцію, і коли я перетворюю їх назад, використовуючи ті самі методи, вибране керівництво є не тим, яке було вставлено. Що перетворює орієнтир? Все, що я зробив, - скопіював код зверху.
всдев

@JonathanOliver Не могли б ви поділитися кодом для функції BinaryToGuid ()?
Арун Аванафан

27

char (36) був би хорошим вибором. Також може бути використана функція UUID () MySQL, яка повертає 36-символьний текстовий формат (шістнадцятковий з дефісами), який може бути використаний для отримання таких ідентифікаторів з db.


19

"Краще" залежить від того, для чого ви оптимізуєте.

Наскільки ви дбаєте про розмір / продуктивність сховища та простість розробки? Що ще важливіше - ви генеруєте достатню кількість GUID або вибираєте їх досить часто, щоб це було важливо?

Якщо відповідь "ні", char(36)це більш ніж добре, і зберігання / отримання GUID-файлів робить простою. В іншому випадку binary(16)це розумно, але вам доведеться спиратися на MySQL та / або вашу мову програмування на вибір, щоб конвертувати назад і назад зі звичайного представлення рядків.


2
Якщо ви розміщуєте програмне забезпечення (наприклад, веб-сторінку) і не продаєте / не встановлюєте у клієнта, ви завжди можете почати з char (36) для легкої розробки на ранній стадії програмного забезпечення та мутувати на більш компактний формату, коли система зростає у використанні та починає потребувати оптимізації.
Хаві Монтеро

1
Найбільша нижня сторона набагато більшого знаку (36) - скільки місця займає індекс. Якщо у базі даних є велика кількість записів, ви збільшуєте подвоєння розміру індексу.
bpeikes


7

Програма GuidToBinary, розміщена KCD, повинна бути налаштована для врахування бітового макета часової позначки в рядку GUID. Якщо рядок являє собою UUID версії 1, як і ті, що повертаються програмою uuid () mysql, то часові компоненти вбудовуються в літери 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 поставив би його перед 3-м ідентифікатором (3e перед dc, останні байти з першого блоку).

Правильним порядком для індексації було б:

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

Додаткову інформацію див. У цій статті: http://mysql.rjweb.org/doc.php/uuid

*** зауважте, що я не розділяю версію версії від 12-ти бітних часових позначок. Це прикол D з вашого прикладу. Я просто кидаю його попереду. Отже, моя бінарна послідовність закінчується DEFG9ABC тощо. Це означає, що всі мої індексовані UUID починаються з одного і того ж клювання. Стаття робить те саме.


чи це мета економії місця для зберігання? або зробити їх сортування корисним?
MD004

1
@ MD004. Це створює кращий індекс сортування. Простір залишається таким же.
bigh_29

5

Для тих, хто просто натрапляє на це, зараз існує набагато краща альтернатива, як свідчать дослідження Перкона.

Він складається з реорганізації фрагментів UUID для оптимальної індексації, а потім перетворення в бінарний для зменшеного зберігання.

Прочитайте повну статтю тут


Я читав цю статтю раніше. Я вважаю це дуже цікавим, але тоді як нам виконати запит, якщо ми хочемо фільтрувати за ідентифікатором, який є двійковим? Я думаю, нам потрібно ще раз накласти шістнадцятку, а потім застосувати критерії. Це так вимогливо? Чому зберігати двійкові (16) (впевнені, що це краще, ніж варчар (36)) замість bigint з 8 байтів?
Максим Децим

2
Є оновлена ​​стаття від MariaDB, яка повинна відповісти на ваше запитання mariadb.com/kb/uk/mariadb/guiduuid-performance
сонний

fwiw, 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 ;

-4

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

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


2
Запуск цього запиту показує, що CAST перетворює рядок uuid в байти ASCII: set @a = uuid (); виберіть @a, шістнадцятковий (литий (@a ЯК БІНАР (16))); Я отримую 16f20d98-9760-11e4-b981-feb7b39d48d6: 3136663230643938 2D 39373630 2D 3131 (пробіли додано для форматування). 0x31 = ascii 1, 0x36 = ascii 6. Ми навіть отримуємо 0x2D, ​​що є дефісом. Це не сильно відрізняється від того, щоб просто зберігати вказівник як рядок, за винятком того, що ви обрізаєте рядок на 16-му символі, який викреслює ту частину ідентифікатора, яка є специфічною для машини.
bigh_29

Так, це просто усічення. select CAST("hello world, this is as long as uiid" AS BINARY(16));виробляєhello world, thi
MD004
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.