Как стать автором
Обновить

Бизнес-ключ и суррогатный ключ нужны оба

Уровень сложностиСредний
Время на прочтение4 мин
Количество просмотров5K
Всего голосов 9: ↑8 и ↓1+11
Комментарии31

Комментарии 31

Autoincrement уже не используют. Все только распределённые базы проектируют.

Даже в распределенной БД возможно использовать целочисленные ключи с автоинкрементом и сохранять их уникальность. Пример — Microsoft Active Directory, концевая часть SID в домене (называется RID). Но, конечно — с определенными ограничениями: там, хоть и весьма изредка, но нужен централизованный арбитр (RID master).
PS Если кому интересны подробности — готов рассказать.

А выскажу непопулярное мнение и скажу, что я согласен с тем экспертом, с которым вы спорите.

Переходя к суррогатному ключу, вы просто заметаете под ковёр проблему с неполным анализом предметной области и неправильным проектированием логической структуры базы данных. Если у вас повторяются ИНН, а вы их используете в качестве ключа, то это вам всё равно отольётся в другом месте. Например, это может значить, что ваши аналитики не понимают разницу между налогоплательщиком и юридическим лицом (организацией в целом и набором её филиалов). Так что суррогатный ключ – это чаще всего способ спихнуть проблемы проектирования на стадию эксплуатации. Результатом часто оказываются повторяющиеся строки, описывающие одну и ту же сущность, но с разными ключами.

Хотя всё, конечно, имеет свои границы, и в некоторых случаях суррогатный ключ является наименьшим злом.

Я перепутал: имел в виду не ИНН, а СНИЛС.

Тут история в том, что в реальной жизни не так много естественных идентификаторов, которые никогда не меняются. ФИО? Фамилия меняется при замужестве (и иногда разводе), причем фамилию может сменить не только женщина. Имя - тоже можно поменять. Номер паспорта - меняется при смене документа. Номер телефона - тоже меняется. Мало того, номер телефона еще и может выдаваться повторно другому человеку! Что осталось - ИНН? СНИЛС? Так не во всех информационных системах они нужны и будут вообще предусмотрены.

А, например, такой идентификатор как "номер договора" чаще всего является вытащенным "наружу" суррогатным ключом, которому придан бизнес-смысл (то есть он генерируется внутри ИС исходя из требования уникальности плюс какой-то формат, типа: год/месяц/день/код партнера/код офиса/прочее).

Номер договора, может, и является вытащенным наружу суррогатным ключом, но он имеет юридическое значение, то есть значим в предметной области сам по себе.

А что касается человека как субъекта, то у него просто вообще нет естественного ключа. Например, человек может выдавать себя за другое лицо. Так что с людьми надо действовать тоньше и заменять понятие "человек" на, например, "налогоплательщик" или "клиент"; либо же действительно использовать суррогатный ключ.

проблему с неполным анализом предметной области

Ларчик просто открывается. Невозможно полностью проанализировать предметную область (я про реальные области/системы) так, чтобы спроектировать для неё идеальную логическую структуру.
Элементарное доказательство - невозможно предсказать завтрашние изменения предметной области. И даже вчерашние изменения, потому что вы сегодня просто не имеете знаний о том, что было вчера. В реальном мире невозможно учесть всё.

Кстати, для меня тоже пару лет назад стало неприятным откровением, что не получится легко и непринуждённо использовать СНИЛС как идентификатор человека

А реально были случаи с коллизией СНИЛС'ов? Аналитики хватаются за сердце. :)

Например, это может значить, что ваши аналитики не понимают разницу между налогоплательщиком и юридическим лицом

А если они понимают разницу, а ключи всё равно повторяются? Строить бизнес-ключ из всей цепочки снилс+инн+фио+(все смены фио)? Но это путь в никуда и тоже не решает проблемы внезапной смены любой части ключа.
Результатом часто оказываются повторяющиеся строки, описывающие одну и ту же сущность, но с разными ключами. Причём в случае с суррогатным ключом понятно как это разруливать, то в случае естественного только мастырить костыли разной степени уродливости.

Может быть это прозвучит неожиданно, но ВСЕ бизнес-ключи это такие же суррогатные ключи, имеющие инкрементальную природу, только они сделаны для учета вне компьютеров.


Из этого есть не совсем очевидный вывод — использовать бизнес-ключи в качестве первичного ключа в компьютерной системе можно только для той сущности, для которой они являются ключом в некомпьютерной системе. То есть ИНН можно использовать только для идентификации налогоплательщиков в юридическом смысле (разные ИНН — разные налогоплательщики), но не для идентификации людей. Иначе это просто неправильная модель данных. И номер паспорта тоже нельзя. Можно использовать только как возможно уникальный атрибут объекта, который можно обновить, не меняя первичный ключ.


Первичный ключ нужен самой системе для идентификации и отличия объектов внутри системы. Любые 2 физических объекта могут иметь абсолютно одинаковые характеристики в пределах точности измерений, это следствие законов физики в нашей Вселенной. Поэтому принципиально невозможно назначить какие-либо измеримые характеристики для отличия одного объекта от других. А самый простой способ отличения 2 одинаковых объектов — это пронумеровать их по порядку и назначить каждому объекту свой номер. Если ИНН, паспорта или номера телефонов назначаете не вы, то вы не можете гарантировать, что другая система, которая это делает, не назначит вашему объекту другой номер. Поэтому использовать их для идентификации внутри вашей системы неправильно.


Поэтому внутренний искусственный автоинкрементный ключ это самое правильное решение.

По-моему, в данном случае из верных наблюдений делается неверный вывод. Именно в целях правильного интерконнекта с другими системами и используются естественные ключи (ИНН в налоговом разрезе и т.д.)

Для интерконнекта с другими системами вам совершенно не нужно использовать его как первичный ключ внутри своей. А использовать его как первичный ключ Person вообще логически неправильно.

Я не предлагал использовать ИНН как ключ для Person.

Вообще, Person - неправильно выбранная сущность, если это не база спецслужб.

Так неважно, использовать его как ключ для любой другой сущности кроме "Налогоплательщик" неправильно. В статье правильно написано про несколько бизнес-ключей у сущности. Вот вы пишете про повторяющиеся строки. Ок, вы сделали ИНН первичным ключом, решили проблему дубликатов для ИНН, но проблема дубликатов для номера паспорта или СНИЛС остается, и решать ее надо будет другим способом. Тот же способ можно использовать и для ИНН или другого бизнес-ключа.


Вообще, Person — неправильно выбранная сущность

Нет, в большинстве случаев клиенты фирмы это как раз люди. Person и Client можно считать синонимами.

Зачем это нужно? Чтобы раздражать клиентов, желающих вести несколько отдельных договорных историй? Гордо заявляя: "О! Василий Петрович Пупкин! Мы видим, что раньше вы уже пользовались нашими услугами вместе со своей любовницей. Рады теперь приветствовать вас и вашу семью!"

Даже в чисто правовом отношении, очень немногие поставщики вправе устанавливать личность клиента.

А уж когда доходит до объединения баз - просто песня. Я в своё время пользовался в одной квартире услугами двух разных интернет-провайдеров (для надёжности), а они возьми да и объединись. И начали сводить базу. Я больше полугода и несколько полных человеко-дней тогда потратил на общение с клиентской службой по поводу возникших глюков.

Зачем это нужно?

Для правильной модели данных. И избегания проблем вида "ИНН нам не нужен, но без него мы не сможем вас зарегистрировать в нашем интернет-магазине, идите сначала получайте".


Чтобы раздражать клиентов, желающих вести несколько отдельных договорных историй?

Не понимаю, как из автоинкрементного первичного ключа в таблице, следует появление нескольких историй, если это не предусмотрено бизнес-логикой. Если у вас есть бизнес-требование по одной истории на клиента, а приложение позволяет сохранять две, значит программный код в приложении написан неправильно. Ключи в базе данных тут ни при чем. Нет, уникальность первичного ключа это не замена нужных проверок в приложении.


очень немногие поставщики вправе устанавливать личность клиента

Я вроде и не предлагал устанавливать личность клиента. Я предлагал назначать ему внутренний автоинкрементный идентификатор вместо ИНН или номера паспорта.


Я в своё время пользовался в одной квартире услугами двух разных интернет-провайдеров

Не понимаю, что это должно доказывать относительно темы разговора. Базы можно прекрасно объединять и с искусственными ключами.

Я в данном случае иллюстрировал мысль о том, что Person - вообще плохая сущность для обычных применений.

Мне нравится этот комментарий. То есть номер документа (паспорта, свидетельства о рождении, водительских прав и т.п.) можно использовать только как идентификатор самого документа, но не человека. Логично.

Но, опять же. Если нам нужно работать с разнородными документами, то может оказаться логичным иметь суррогатный ключ для абстрактной сущности "документ". А уже конкретные документы будут иметь свои уникальные атрибуты - серия, номер, дата выдачи, место выдачи и т.д.

Мне кажется общее правило: для Entity использовать суррогатный ключ, для Value - естественный. Но у него полно исключений. Скажем, когда данные участвуют в обмене с внешними системами. Например ребования по работе с персональными данными и анонимизация могут вынудить ввести суррогатные ключи вместо естественных.

Вот, кстати, да. Выгрузка данных с "боевой" системы на тестовую - более чем реальный кейс. Заменить ИНН фейковыми намного сложнее, в случае когда это ключ в таблице, по которому на неё ссылаются другие таблицы. В случае суррогатного ключа этой проблемы нет.

Да и в целом согласен с автором.

Всегда использую суррогатные ключи, и только одного типа данных. Опыт показал: всё, что казалось естественным ключом на первый взгляд, по факту оказалось не очень уж и стабильным и непротиворечивым. Чего стоят разные ИНН у одного человека, сохранение госномера за владельцем при продаже авто, присвоение сетевых номеров списанных железнодорожных вагонов новым, смена БИК при реорганизации банков и т.д. и т.п. А сколько радости некоторые испытали на своей шкуре, когда узнали, что в КПП могут быть буквы, и в тип int оно никак не лезет. В итоге, ну их всех, эти естественные ключи.

Суррогатный ключ хорош, когда функция, генерящая его по бизнес-ключу дает стабильный результат. Это позволяет одновременно лить данные в факты и справочники или получать значение суррогатного ключа без обращения к hub-таблице. UUIDv7 это позволяет? Как я понял - нет.

UUIDv7 генерится не по бизнес-ключу, а независимо от него. Ничто не мешает использовать UUIDv7 для того, чтобы "одновременно лить данные в факты и справочники": сгенерировали UUIDv7 один раз, сохранили в памяти и затем используем сохраненное значение сколько угодно раз. Нужно лишь позаботиться о том, чтобы сгоряча одно значение UUIDv7 не присвоить разным объектам.

Сергей, в какой памяти? Сегодня пришли транзакции по клиенту, завтра, через год. Согласно картинке из вашей статьи система должна перекодировать код клиента в суррогатный ключ. Варианта два:

  • использовать счетчик или аналог (предполагаю uuidv7 это аналог) для вставки записей в справочник (hub-таблицу) и lookup по справочнику при вставке в таблицы фактов

  • использовать хэш функцию (можно лить одновременно и в факты и справочники), но надо обрабатывать коллизии

Поясните, как организовать регламентную загрузку с использованием uuidv7 ?

Вот этот вариант прекрасно работает:


использовать счетчик или аналог (предполагаю uuidv7 это аналог) для вставки записей в справочник (hub-таблицу) и lookup по справочнику при вставке в таблицы фактов

Надо понимать, что если у вас пришли транзакции по одному клиенту — то и lookup по справочнику клиентов можно делать один раз, а не по разу на транзакцию.

Если в пришедшей транзакции есть имеющийся в хабе актуальный бизнес-ключ клиента, то второй суррогатный ключ для этого клиента не нужно генерировать. Если же у клиента изменился бизнес-ключ, то соответствующую запись нужно добавить в хаб клиентов.

Генерить суррогатные ключи всем неизвестным клиентам на основании только транзакции - это опасная затея. Ведь если у клиента изменился бизнес-ключ (а мы об этом не знали), и транзакция пришла с новым бизнес-ключом, то на основании этой транзакции для того же самого клиента будет сгенерирован второй суррогатный ключ. Пока мы этого не узнаем и не исправим, у нас будут в учете две разных версии правды об этом клиенте.

Например, если пришли деньги клиенту, которого нет в хабе клиентов, то они должны упасть на "неопознанные суммы" и лежать там до ввода этого клиента. Если же его не ввели в отпущенный срок, то деньги должны вернуться обратно.

Хотел бы обратить внимание на то, что бизнес-ключи идентифицируют не только клиентов, автомобили, недвижимость и т.п. Они могут приходить из систем-источников или из нормативных актов и идентифицировать такие виртуальные вещи, как договоры, документы, операции, счета, коды классификации и т.п.

Из описания непонятно, в каком виде приходят данные, в каком количестве, и в чем заключается вопрос. Для разных ситуаций будет разное оптимальное решение.


Ситуации, когда lookup-запрос влияет на производительность, появляются при реально большой нагрузке, в большинстве приложений это не играет роли.
В каких-то случаях можно загружать соответствие [id -> бизнес-ключ] для всех клиентов в кеш или в оперативную память приложения, вряд ли клиентов несколько миллиардов. Или использовать least recently used cache.


Если в данных транзакций есть только бизнес-ключ клиента, а данные клиентов приходят где-то отдельно, то как сказали выше, просто так сохранять транзакцию неправильно, нужно делать проверку, что этот клиент есть в базе. Если его нет, то записывать предупреждение в лог или помечать такую транзакцию для ручной обработки.


Если данные клиента приходят вместе с транзакцией, сначала сохраняем клиента и получаем id, или проверяем что он уже есть в базе и тоже получаем id, потом сохраняем транзакцию с этим id.


Если вы подразумеваете, что с бизнес-ключами база сама будет выбрасывать исключение, если мы сохраним транзакцию для клиента, которого нет в базе, то у этого есть свои недостатки — усложнение кода приложения для обработки этого исключения, невозможность сохранить несколько записей в одном insert, и т.д.

глобальный стандартный идентификатор с понятным для человека значением - утопия)

Спасибо за статью. Пара вопросов:

  1. Что означает поле source? Это ID самой первой версии записи? И все последующие версии хранят внутри себя ссылку на неё?

  2. Как достать самую актуальную (текущую) версию? Смотреть на datetime_before is null?

  1. source - это ID системы-источника данных. На самом деле приведенная на рисунке в начале статьи структура данных не единственная возможная. Например, таблица Hub может быть нормализована (разбита) на несколько таблиц, то есть, для данной сущности и для каждой системы-источника может быть создана отдельная таблица с бизнес-ключами (атрибут в методологии Anchor Modeling), ссылающаяся на таблицу с полем surrogate_key и record_datetime. Это, возможно, обеспечит меньшую сложность алгоритмов.

  2. datetime_before is null или datetime_before = '2999-12-31'или какая-то другая условная дата, обозначающая бесконечность. Это дает актуальность в реальном мире (Valid time). Кроме того, нужно выбрать запись с максимальным значением record_datetime. Это дает актуальность в учете (Transaction time). См. https://en.wikipedia.org/wiki/Temporal_database

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории