Контекст
Я розробляю базу даних (на PostgreSQL 9.6), яка буде зберігати дані з розподіленої програми. Через розповсюджений характер програми я не можу використовувати цілі числа автоматичного збільшення ( SERIAL
) як основний ключ через потенційні перегони.
Природним рішенням є використання UUID або глобально унікальний ідентифікатор. Postgres поставляється із вбудованим UUID
типом , який ідеально підходить.
Проблема, яку я маю з UUID, пов’язана з налагодженням: це непридатний для людини рядок. Ідентифікатор ff53e96d-5fd7-4450-bc99-111b91875ec5
нічого мені не говорить, тоді як ACC-f8kJd9xKCd
, хоча не гарантовано є унікальним, говорить мені, що я маю справу з ACC
об'єктом.
З точки зору програмування, звичайним є налагодження запитів додатків, що стосуються декількох різних об'єктів. Припустимо, програміст неправильно здійснює пошук ACC
об’єкта (акаунта) у ORD
таблиці (замовлення). Завдяки читаному людиною ідентифікатору програміст миттєво ідентифікує проблему, використовуючи UUID, він витратить якийсь час на з'ясування того, що не так.
Мені не потрібна «гарантована» унікальність UUID; Я дійсно потрібна кімната для генерації ключів без конфліктів, але UUID є надмірністю. Крім того, найгірший сценарій, це не було б кінцем світу, якби сталося зіткнення (база даних відкидає його, і програма може відновитись). Тож, з точки зору компромісу, менший, але зручний для людини ідентифікатор був би ідеальним рішенням для мого використання.
Ідентифікація об'єктів програми
Ідентифікатор, до якого я придумав, має такий формат:, {domain}-{string}
де {domain}
замінюється об’єктним доменом (рахунок, замовлення, продукт) і {string}
є випадково генерованим рядком. У деяких випадках може бути навіть сенс вставити а {sub-domain}
перед випадковим рядком. Давайте ігноруємо довжину {domain}
та {string}
з метою гарантування унікальності.
Формат може мати фіксований розмір, якщо він допомагає індексувати / виконувати запити.
Проблема
Знаючи це:
- Я хочу мати первинні ключі у такому форматі
ACC-f8kJd9xKCd
. - Ці первинні ключі будуть частиною декількох таблиць.
- Усі ці ключі будуть використовуватися в кількох з'єднаннях / відносинах, в базі даних 6NF.
- Більшість таблиць мають розмір середнього та великого розміру (середнє ~ 1 М рядків; найбільші - ~ 100 М рядків).
Що стосується продуктивності, який найкращий спосіб зберігати цей ключ?
Нижче наведено чотири можливі рішення, але оскільки у мене мало досвіду роботи з базами даних, я не впевнений, який (якщо такий є) найкращий.
Розглянуті рішення
1. Зберігати як рядок ( VARCHAR
)
(Постгрес не має різниці між CHAR(n)
і VARCHAR(n)
, тому я ігнорую CHAR
).
Після деяких досліджень я з'ясував, що порівняння рядків VARCHAR
, особливо при операціях з'єднання, повільніше, ніж використання INTEGER
. Це має сенс, але чи варто мені турбуватися в такому масштабі?
2. Зберігати як двійковий ( bytea
)
На відміну від Postgres, MySQL не має нативного UUID
типу. Існує кілька публікацій, що пояснюють, як зберігати UUID за допомогою 16-байтового BINARY
поля, а не 36-байтового VARCHAR
. Ці повідомлення дали мені ідею зберігати ключ як бінарний ( bytea
на Postgres).
Це економить розмір, але я більше переймаюся продуктивністю. Мені мало шансів знайти пояснення, яке порівняння швидше: двійкові або рядкові. Я вважаю, що бінарні порівняння проходять швидше. Якщо вони є, то bytea
, мабуть, краще, ніж VARCHAR
, хоча програміст тепер повинен кожного разу кодувати / декодувати дані.
Я можу помилятися, але думаю, що обидва bytea
і VARCHAR
порівнятиму (рівність) байт за байтом (або символом за символом). Чи є спосіб "пропустити" це покрокове порівняння і просто порівняти "всю справу"? (Я не вважаю, але перевірка не коштує).
Я думаю, що зберігання як bytea
найкраще рішення, але мені цікаво, чи є інші альтернативи, які я ігнорую. Крім того, справедливо те саме занепокоєння, яке я висловив щодо рішення 1: чи достатньо витрат на порівняння, про які я повинен хвилюватися?
«Креативні» рішення
Я придумав два дуже "креативні" рішення, які могли б працювати, я просто не впевнений, в якій мірі (тобто, якщо у мене виникнуть проблеми з їх масштабуванням на більш ніж пару тисяч рядків у таблиці).
3. Зберігайте як, UUID
але з доданою до нього "етикеткою"
Основна причина не використовувати UUID - це те, щоб програмісти могли краще налагоджувати додаток. Але що робити, якщо ми можемо використовувати обидва: база даних зберігає всі ключі UUID
лише як s, але вона обгортає об'єкт до / після запитів.
Наприклад, програміст запитує ACC-{UUID}
, база даних ігнорує ACC-
частину, отримує результати та повертає їх усі як {domain}-{UUID}
.
Можливо, це було б можливо з деяким хакером із збереженими процедурами чи функціями, але на думку приходять деякі питання:
- Це (видалення / додавання домену при кожному запиті) суттєве накладне покриття?
- Це навіть можливо?
Я ніколи раніше не використовував збережені процедури чи функції, тому не знаю, чи це можливо. Може хтось пролити трохи світла? Якщо я можу додати прозорий шар між програмістом і збереженими даними, це здається ідеальним рішенням.
4. (Мій улюблений) Зберігайте як IPv6 cidr
Так, ви правильно прочитали. Виявляється, формат адреси IPv6 ідеально вирішує мою проблему .
- Я можу додати домени та субдомени в перші кілька октетів, а інші використовувати як випадковий рядок.
- Швидкість зіткнення в порядку. (Я б не використовував 2 ^ 128, але все одно гаразд.)
- Порівняння рівності (сподіваємось) оптимізовано, тому я можу отримати кращу ефективність, ніж просто використовувати
bytea
. - Я фактично можу виконати цікаві порівняння, наприклад
contains
, залежно від того, як представлені домени та їх ієрархія.
Наприклад, припустимо, я використовую код 0000
для представлення домену "products". Ключ 0000:0db8:85a3:0000:0000:8a2e:0370:7334
представляв би продукт 0db8:85a3:0000:0000:8a2e:0370:7334
.
Тут головне питання: порівняно з тим bytea
, чи є якась основна перевага чи недолік у використанні cidr
типу даних?
varchar
серед багатьох інших проблем. Я не знав про домени pg, про що чудово дізнатися. Я бачу, що домени використовуються для перевірки, якщо для даного запиту використовується правильний об'єкт, але він все ще покладається на наявність нецілого індексу. Не впевнений, чи існує "безпечний" спосіб використання serial
тут (без одного кроку блокування).
varchar
. Розглянемо його як FK
integer
тип і додайте до нього таблицю пошуку. Таким чином, ви можете мати як читабельність людини, так і захистите ваш композит PK
від аномалій вставки / оновлення (введення неіснуючого домену).
text
є кращим varchar
. Подивіться на depesz.com/2010/03/02/charx-vs-varcharx-vs-varchar-vs-text та postgresql.org/docs/current/static/datatype-character.html
ACC-f8kJd9xKCd
. ”← Це, мабуть, є роботою для старого доброго композитора ПЕРШИЙ КЛЮЧ .