контекст
Я проектирую базу данных (на 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
)
(Postgres не делает разницы между 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
для представления домена «продукты». Ключ 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
. «Похоже, это работа для старого доброго композитного PRIMARY KEY .