Продуктивність UUID в MySQL?


86

Ми розглядаємо можливість використання значень UUID як первинних ключів для нашої бази даних MySQL. Дані, що вставляються, генеруються з десятків, сотень або навіть тисяч віддалених комп’ютерів і вставляються зі швидкістю 100-40 000 вставок в секунду, і ми ніколи не будемо робити оновлення.

Сама база даних зазвичай отримує близько 50 мільйонів записів до того, як ми почнемо відбирати дані, тож не масивна база даних, але не крихітна. Ми також плануємо працювати на InnoDB, хоча ми готові змінити це, якщо є кращий двигун для того, що ми робимо.

Ми були готові піти з UUID типу 4 Java, але під час тестування спостерігали дивну поведінку. По-перше, ми зберігаємо як varchar (36), і тепер я розумію, що нам було б краще використовувати двійковий файл (16) - хоча наскільки краще, я не впевнений.

Більше питання: наскільки погано ці випадкові дані псують індекс, коли ми маємо 50 мільйонів записів? Чи було б нам краще, якби ми використали, наприклад, UUID типу 1, де крайні ліві біти були позначені часом? А може, нам слід повністю відмовитися від UUID і розглянути первинні ключі auto_increment?

Я шукаю загальні думки / поради щодо продуктивності різних типів UUID, коли вони зберігаються як індекс / первинний ключ у MySQL. Дякую!


2
відсутня одна важлива деталь: первинні ключі генеруються сервером реєстрації або самими клієнтськими машинами?

1
@hop їх генерують 10-1000 клієнтів, які вставляють дані
Патрік Лайтбоді

Де вам потрібна універсальна унікальність у вашому сценарії? Моя порада - дотримуватися auto_increment і використовувати окреме поле для опису віддаленого комп’ютера, який надсилає дані. Не потрібно тут винаходити колесо.
Теодор Зографос,

Відповіді:


36

UUID - це універсальний унікальний ідентифікатор. Це універсальна частина, яку ви повинні тут розглянути.

Вам справді потрібні посвідчення особи, щоб бути універсально унікальними? Якщо так, то UUID можуть бути вашим єдиним вибором.

Я настійно рекомендую , що якщо ви робите використання UUID , ви зберігаєте їх у вигляді числа , а не у вигляді рядка. Якщо у вас 50 мільйонів записів, тоді економія місця на сховищі покращить вашу продуктивність (хоча я не міг сказати, на скільки).

Якщо ваші ідентифікатори не повинні бути універсально унікальними, то я не думаю, що ви можете зробити набагато краще, ніж просто використовувати auto_increment, який гарантує, що ідентифікатори будуть унікальними в таблиці (оскільки значення буде збільшуватися щоразу)


2
Цікавий момент; це дозволить паралельно генерувати ключі. Я вважаю, що це збільшило б ефективність генерації ключів. Однак ви обираєте продуктивність INSERT, а не SELECT, якщо для зберігання UUID використовуєте VARCHAR. Ви точно повинні вибрати VARBINARY для зберігання, щоб забезпечити продуктивність SELECT. Додатковий крок може вплинути на продуктивність INSERT, але ви отримаєте винагороду завдяки покращенню продуктивності SELECT.
Dancrumb 02.03.10

12
Врешті-решт, ми провели порівняльний аналіз реальних даних, і GUID без клавіш було досить швидким, GUID з клавішами було жахливим (навіть якщо вони зберігалися як BINARY), а int w / AUTO_COMPLETE був найшвидшим. Я думаю, що в нашому випадку ми дійсно пропустили ліс з дерев, оскільки генерація послідовностей видалася несуттєвою порівняно з витратами на зберігання більшої кількості даних + маючи справді безглуздий BTREE через випадковість GUID
Patrick Lightbody

1
зберігати як число означає зберігання у двійковому форматі? але двійковий формат для людини нечитабельний. Це повільно, оскільки великі байти первинного ключа uuid? Якщо це так, тоді я міг би зберегти автоматичне збільшення в іншому стовпці для uuid. Тоді продуктивність не постраждає. Я правий?
Chamnap

4
Строго кажучи, UUID є універсальним унікальним, що означає, що він ніколи не з'явиться ніде в світі. Це вам потрібно, лише якщо ви публічно ділитесь своїми даними. Що стосується зберігання UUID як числа, я не маю на увазі у binaryформаті. Я маю на увазі як 128-бітове число, а не 288-бітовий рядок. Наприклад, слово "привіт" в ASCII - 68 65 6C 6C 6Fце число 448 378 203 247. Для зберігання рядка '68656C6C6F' потрібно 10 байт. Число 448 378 203 247 вимагає лише 5. Загалом, якщо вам дійсно не потрібен перший U в UUID, ви не можете зробити набагато краще, ніжauto_increment
Dancrumb

1
@Chamnap: Запропонуйте поставити запитання щодо переповнення стека: o)
Dancrumb

78

На моїй роботі ми використовуємо UUID як ПК. Що я можу сказати вам з досвіду, це НЕ ВИКОРИСТОВУВАТИ ЇХ як ПК (SQL Server, до речі).

Це одна з тих речей, що коли у вас менше 1000 записів, це нормально, але коли у вас мільйони, це найгірше, що ви можете зробити. Чому? Оскільки UUID не є послідовними, тому кожного разу, коли вставляється новий запис, MSSQL потрібно перейти до правильної сторінки, щоб вставити запис, а потім вставити запис. Справді потворним наслідком цього є те, що сторінки закінчуються різними розмірами, і вони закінчуються фрагментарно, тож тепер нам доводиться періодично дефрагментувати.

Коли ви використовуєте автоінкремент, MSSQL завжди переходить на останню сторінку, і ви отримуєте однаково великі сторінки (теоретично), тому ефективність відбору цих запитів набагато краща (також тому, що INSERT не блокують таблицю / сторінку для так довго).

Однак велика перевага використання UUID як ПК полягає в тому, що якщо у нас є кластери БД, не буде конфліктів при об’єднанні.

Я б рекомендував наступну модель: 1. Ідентифікація PK INT 2. Додатковий стовпець, автоматично згенерований як UUID.

Таким чином, процес злиття можливий (UUID буде вашим РЕАЛЬНИМ ключем, тоді як PK буде просто тимчасовим, що забезпечує вам хорошу продуктивність).

ПРИМІТКА. Найкращим рішенням є використання NEWSEQUENTIALID (як я вже говорив у коментарях), але для застарілої програми, яка не має багато часу на рефакторинг (і навіть гірше, не контролюючи всі вставки), це неможливо зробити. Але дійсно станом на 2017 рік, я б сказав, що найкращим рішенням тут є NEWSEQUENTIALID або створення Guid.Comb з NHibernate.

Сподіваюся, це допомагає


Я насправді не знаю, що означають ці терміни, але справа в тому, що індекси потрібно переіндексувати щомісяця. Якщо те, що ви згадали, усуває завдання переіндексації, я не знаю, але можу запитати.
Kat Lim Ruiz

3
Щось, про що я думав, це те, що це може не спрацювати так добре для стосунків батьків і дітей. У цьому випадку, я думаю, вам доведеться додати в дочірню таблицю: parent-pk, parent-guide. Інакше ви можете втратити посилання між базами даних. Я не надто над цим замислювався і не робив жодного прикладу, але це може знадобитися
Kat Lim Ruiz

4
@KatLimRuiz на сервері sql ви можете використовувати NEWSEQUENTIALID () technet.microsoft.com/en-us/library/ms189786.aspx, щоб уникнути проблеми з продуктивністю
giammin

Дійсно, але NEWSEQUENTIALID працює лише за замовчуванням. Тож вам потрібно розробити весь DAL навколо цього, що нормально для нових проектів, але не так просто для великої спадщини
Kat Lim Ruiz

@KatLimRuiz геній. Це чудовий компроміс
jmgunn87

26

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

Щодо продуктивності, коротко :

UUID, подібний до наведеного вище, має довжину 36 символів, включаючи тире. Якщо ви зберігаєте цей VARCHAR (36), ви значно зменшите ефективність порівняння. Це ваш основний ключ, ви не хочете, щоб він був повільним.

На своєму бітовому рівні UUID дорівнює 128 бітам, що означає, що він вміщуватиметься в 16 байт. Зверніть увагу, це не дуже зручно для читання, але він буде зберігати низький рівень зберігання і лише в 4 рази перевищує 32-розрядний int, або 2 в рази більше, ніж 64-розрядний int. Я буду використовувати VARBINARY (16) Теоретично це може працювати без великих накладних витрат.

Рекомендую прочитати наступні два дописи:

Я вважаю, що вони відповідають на ваше запитання.


2
Власне, я прочитав обидві ці статті до того, як опублікувати це запитання, і досі не мав тут хорошої відповіді. Наприклад, ні говорити про тип 1 проти типу 4 UUIDS :(
Патрік Лайтбоді

Справедливе, що я оновив свою відповідь. Я не думаю, що це дає занадто багато додаткового розуміння.
Кайл Розендо

@Patrick: Ви ставите занадто багато різних тем у своєму питанні.

1
9 років потому, але для нащадків також слід зазначити, що на відміну від цілочисельних ідентифікаторів, програми можуть безпечно генерувати UUID, повністю видаляючи генерацію з бази даних. Маніпуляція UUID-кодами для оптимізації продуктивності (заснована на позначці часу, але модифікована, щоб їх можна було наївно сортувати) набагато простіша практично будь-якою мовою, відмінною від SQL. На щастя, майже всі бази даних сьогодні (включаючи MySQL) обробляють первинні ключі UUID набагато краще, ніж раніше.
Miles

5

Я схильний уникати UUID просто тому, що це біль, яку потрібно зберігати, і біль, яка використовується як первинний ключ, але є переваги. Головна з них - це УНІКАЛЬНІ.

Зазвичай я вирішую проблему та уникаю UUID, використовуючи подвійні ключові поля.

КОЛЕКТОР = УНІКАЛЬНИЙ, ПРИПИСАНИЙ МАШИНІ

ID = ЗАПИС, ЗБЕРЕЖЕНИЙ КОЛЕКТОРОМ (поле auto_inc)

Це пропонує мені дві речі. Швидкість полів автоматичного включення та унікальність даних, що зберігаються в центральному місці після їх збору та згрупування. Я також знаю під час перегляду даних, де вони були зібрані, що часто є досить важливим для моїх потреб.

Я бачив багато випадків, маючи справу з іншими наборами даних для клієнтів, коли вони вирішили використовувати UUID, але тоді все ще є поле для збору даних, що насправді є марною тратою зусиль. Просто використовуючи два (або більше, якщо потрібно) полів, оскільки ваш ключ справді допомагає.

Я щойно бачив занадто багато хітів продуктивності за допомогою UUID. Вони відчувають себе шахраєм ...


3

Замість того, щоб централізовано генерувати унікальні ключі для кожної вставки, як щодо розподілу блоків ключів для окремих серверів? Коли у них закінчуються ключі, вони можуть запросити новий блок. Тоді ви вирішуєте проблему накладних витрат, підключаючи для кожної вставки.

Сервер ключів підтримує наступний доступний ідентифікатор

  • Сервер 1 вимагає блокування ідентифікатора.
  • Сервер ключів повертає (1,1000)
    Сервер 1 може вставляти 1000 записів, поки йому не буде потрібно запитувати новий блок
  • Сервер 2 запитує індексний блок.
  • Повернення сервера ключів (1001,2000)
  • тощо ...

Ви можете придумати більш досконалу версію, де сервер може запитувати кількість необхідних ключів, або повертати невикористані блоки на сервер ключів, що, звичайно, потрібно буде вести карту використаних / невикористаних блоків.


Цікава пропозиція в теорії. Це було б складно управляти на практиці. Більш практичним рішенням, мабуть, була б відповідь, поставлена ​​Швораком.
Саймон Іст

2

Я б призначив кожному серверу числовий ідентифікатор транзакційним способом. Потім кожен вставлений запис просто автоматично збільшить власний лічильник. Поєднання ServerID та RecordID буде унікальним. Поле ServerID може бути проіндексовано, і майбутня вибрана продуктивність на основі ServerID (за потреби) може бути набагато кращою.


2

Коротка відповідь полягає в тому, що багато баз даних мають проблеми з продуктивністю (зокрема, з великими обсягами INSERT) через конфлікт між їх методом індексування та навмисною ентропією UUID у бітах високого порядку. Є декілька поширених хакерів:

  • оберіть інший тип індексу (наприклад, некластерований на MSSQL), який не проти
  • обміняти дані, щоб перемістити ентропію до бітів нижчого порядку (наприклад, переупорядкувати байти V1 UUID на MySQL)
  • зробіть UUID вторинним ключем із автоматичним збільшенням первинного ключа

... але це все хакі - і, мабуть, крихкі.

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


1

А як щодо деяких UID, створених вручну? Дайте кожному з тисяч серверів ідентифікатор і зробіть первинний ключ комбінованим ключем автоінкременту, MachineID ???


Я думав про це, і, можливо, доведеться запустити деякі тести. Навіть тимчасової локальної послідовності на кожній з 1000 машин у поєднанні з міткою часу може бути достатньо. Приклад: machine_id + temp_seq + мітка часу
Патрік Лайтбоді

Чи можна мати temp_sequence, яка скидає кожну галочку позначки часу? Я не впевнений.
MindStalker 02.03.10

1

Оскільки первинний ключ генерується децентралізовано, ви все одно не можете використовувати auto_increment.

Якщо вам не потрібно приховувати ідентифікацію віддалених машин, використовуйте UUID типу 1 замість UUID. Їх легше генерувати і, принаймні, можуть не зашкодити продуктивності бази даних.

Те саме стосується і varchar (char, дійсно), і бінарного: це може лише допомогти питанням. Це насправді важливо, наскільки покращена продуктивність?


0

Я усвідомлюю, що це питання досить давнє, але я в своєму дослідженні натрапив на нього. З тих пір сталося багато речей (SSD є всюдисущими InnoDB отримував оновлення тощо).

У своєму дослідженні я знайшов цю досить цікаву публікацію про ефективність:

стверджуючи, що через випадковість GUID / UUID дерева індексів можуть стати досить незбалансованими. у MariaDB KB я знайшов інший допис, що пропонує рішення. Але з цього часу новий UUID_TO_BIN подбає про це. Ця функція доступна лише в MySQL (перевірена версія 8.0.18), а не в MariaDB (версія 10.4.10)

TL; DR: Зберігає UUID як перетворені / оптимізовані значення BINARY (16).

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