Нецілі міркування первинного ключа


16

Контекст

Я розробляю базу даних (на 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типу даних?


5
Скільки можливих розподілених вузлів? Чи знаєте ви їх кількість (та назви) достроково? Чи розглядали ви складові (багатоколонні) ПК? Домен (залежно від мого першого запитання), а також звичайний серійний стовпець може бути найменшим, найпростішим та найшвидшим ...
Ервін Брандстеттер

@Phil дякую! @ErwinBrandstetter Що стосується програми, вона розроблена для автоматичного масштабування відповідно до навантаження, тому інформації заздалегідь є дуже мало. Я думав про використання (домен, UUID) як ПК, але це повторить "домен" у всьому, домен все ще буде varcharсеред багатьох інших проблем. Я не знав про домени pg, про що чудово дізнатися. Я бачу, що домени використовуються для перевірки, якщо для даного запиту використовується правильний об'єкт, але він все ще покладається на наявність нецілого індексу. Не впевнений, чи існує "безпечний" спосіб використання serialтут (без одного кроку блокування).
Ренато Сікейра Массаро

1
Домен не обов'язково повинен бути varchar. Розглянемо його як FK integerтип і додайте до нього таблицю пошуку. Таким чином, ви можете мати як читабельність людини, так і захистите ваш композит PKвід аномалій вставки / оновлення (введення неіснуючого домену).
yemet


1
« Я хочу мати первинні ключі у такому форматі ACC-f8kJd9xKCd. ”← Це, мабуть, є роботою для старого доброго композитора ПЕРШИЙ КЛЮЧ .
MDCCL

Відповіді:


5

Використання ltree

Якщо IPV6 працює, чудово. Він не підтримує "ACC". ltreeробить.

Шлях мітки - це послідовність нульових або більше міток, розділених крапками, наприклад L1.L2.L3, що представляє шлях від кореня ієрархічного дерева до конкретного вузла. Довжина контуру мітки повинна бути меншою за 65 кБ, але бажано тримати її менше 2 кБ. На практиці це не є серйозним обмеженням; Наприклад, найдовший шлях мітки в каталозі DMOZ ( http://www.dmoz.org ) становить близько 240 байт.

Ви використовуєте це так,

CREATE EXTENSION ltree;
SELECT replace('ACC-f8kJd9xKCd', '-', '.')::ltree;

Ми створюємо вибіркові дані.

SELECT x, (
  CASE WHEN x%7=0 THEN 'ACC'
    WHEN x%3=0 THEN 'XYZ'
    ELSE 'COM'
  END ||'.'|| md5(x::text)
  )::ltree
FROM generate_series(1,10000) AS t(x);

CREATE INDEX ON foo USING GIST (ltree);
ANALYZE foo;


  x  |                ltree                 
-----+--------------------------------------
   1 | COM.c4ca4238a0b923820dcc509a6f75849b
   2 | COM.c81e728d9d4c2f636f067f89cc14862c
   3 | XYZ.eccbc87e4b5ce2fe28308fd9f2a7baf3
   4 | COM.a87ff679a2f3e71d9181a67b7542122c
   5 | COM.e4da3b7fbbce2345d7772b0674a318d5
   6 | XYZ.1679091c5a880faf6fb5e6087eb1b2dc
   7 | ACC.8f14e45fceea167a5a36dedd4bea2543
   8 | COM.c9f0f895fb98ab9159f51fd0297e236d

І віола ..

                                                          QUERY PLAN                                                          
------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on foo  (cost=103.23..234.91 rows=1414 width=57) (actual time=0.422..0.908 rows=1428 loops=1)
   Recheck Cond: ('ACC'::ltree @> ltree)
   Heap Blocks: exact=114
   ->  Bitmap Index Scan on foo_ltree_idx  (cost=0.00..102.88 rows=1414 width=0) (actual time=0.389..0.389 rows=1428 loops=1)
         Index Cond: ('ACC'::ltree @> ltree)
 Planning time: 0.133 ms
 Execution time: 1.033 ms
(7 rows)

Докладнішу інформацію та операторів див. У документах

Якщо ви створюєте ідентифікатори продукту, я б погодився. Якщо вам потрібно щось створити, я б скористався UUID.


1

Що стосується порівняння продуктивності з bytea. порівняння мережі проводиться в 3 етапи: спочатку на загальних бітах мережевої частини, потім на довжину мережевої частини, а потім на всю немасковану адресу. див .: network_cmp_internal

так що це повинно бути трохи повільніше, ніж байт, який прямує до memcmp. Я провів простий тест на таблиці з 10 мільйонами рядків, які шукають один:

  • використовуючи числовий ідентифікатор (ціле число), це зайняло у мене 1000мс.
  • використовуючи cidr, це зайняло 1300 мс.
  • використовуючи bytea, це зайняло 1250 мс.

Я не можу сказати, що між байт-сайтом і cidr є велика різниця (хоча розрив залишався послідовним) Просто додаткове ifтвердження - здогадайтеся, що це не так вже й погано для 10 м кортежів.

Сподіваюся, що це допоможе - хотілося б почути, що ви нарешті вибрали.

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