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

Пару дней назад я агитировал всеми уважаемого эксперта в хранилищах данных за новый стандарт суррогатных ключей UUIDv7 для высоконагруженных систем, и получил от него ответ:

"На самом деле, и, по моему скромному мнению, НИКАКОЙ суррогатный ключ не является достаточно хорошим, или его не следует использовать. По моему скромному мнению, настоящие и истинные бизнес-ключи (буквенно-цифровые значения с понятным для человека значением) — это путь вперед и правильный способ проектирования, идентификации, архитектуры и связывания данных вместе. Примером этого может быть vehicle identification number (глобальный стандартный идентификатор с понятным для человека значением). Да, он содержит порядковые номера как составную часть, но сам ключ — это гораздо больше.

Для меня все суррогаты (сгенерированные машиной идентификаторы) - бесполезные бессмысленные биты данных. Они служат только для «уникальной идентификации строки/набора данных». После «отсоединения» от данных, которые он представляет, значение ключа становится абсолютно бессмысленным, тогда как настоящий бизнес-ключ (как описано выше) по-прежнему сохраняет свою ценность для людей, даже в качестве отдельного ключа."

Пришлось писать ответное письмо, из которого родилась эта статья.

Действительно, было бы замечательно, если бы один и тот же буквенно-цифровой код мог применяться и для ссылки на внешние данные (например, реестры или классификаторы), и для связи строк в разных таблицах базы данных. Но примеры таких бизнес-ключей (естественных ключей) в виде буквенно-цифровых кодов очень трудно найти, и при ближайшем рассмотрении они оказываются негодными. Например, для меня было неприятной неожиданностью, что российский СНИЛС неуникален (в редких случаях). Кроме того, внешние идентификаторы время от времени изменяются (пример - смена фамилии), и поэтому их невозможно использовать в базах данных без ухищрений. А когда возникла необходимость объединить в одной таблице все виды залогов, то в столбец c VIN (vehicle identification number, идентификационный номер транспортного средства) пришлось добавить кадастровый номер недвижимости, и возникли сомнения в уникальности смешанного идентификатора в столбце. Кроме того, японские автомобили не имеют VIN! У них другая маркировка. Это может быть номер шасси, либо кузова, либо рамы. Вместо общепринятого VIN из 17 цифр и букв, он содержит от 9 до 12 символов.

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

Предпочтительная схема данных, обеспечивающая связь бизнес-ключей с суррогатным ключом, помещена в начале этой статьи. Хаб - это таблица базы данных, относящаяся к какой-то одной сущности (люди или автомобили и т.п.). Хаб содержит связь бизнес-ключей с суррогатным ключом. Одному суррогатному ключу может соответствовать несколько разных бизнес-ключей. Например, человек может идентифицироваться по ИНН, или по ФИО, дате и месту рождения, или по номеру паспорта. Изменчивость бизнес-ключа обеспечивается атрибутами историчности (изменения в реальности) и версионности (изменения в учете), например, datetime_from, datetime_before, record_datetime. Бизнес-ключ может состоять из нескольких атрибутов, например, из фамилии, имени и отчества. Остальные таблицы, содержащие атрибуты сущности или связи данной сущности с другими сущностями, соединяются с хабом по суррогатному ключу.

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

Что касается лучшего типа суррогатного ключа, то теперь это несомненно UUIDv7. Читайте мою статью "Встречайте UUID нового поколения для ключей высоконагруженных систем" и ждите продолжение.

Если же в дополнение к реальному миру и к информационной системе возникает третья сторона - человек (аналитик и пользователь хранилища данных), то возникает необходимость в человекочитаемых идентификаторах. При небольшом количестве возможных значений часто в качестве ключа применяют короткие буквенные или буквенно-цифровые коды, создаваемые вручную, в которых легко возникает путаница. Для UUIDv7 же легко получить человекочитаемый идентификатор длиной 26 символов путем применения кодировки Crockford's Base32. Вот пример такого идентификатора: 01H7K17QKJ5ND32CQS0NB03FXW. Может быть, произносить это и долго, но зато можно легко найти похожий идентификатор, скопировать в буфер обмена, сравнить с другим идентификатором на глаз.

Комментарии (26)


  1. flancer
    12.08.2023 05:59
    -2

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


    1. mvv-rus
      12.08.2023 05:59
      +1

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


  1. Tzimie
    12.08.2023 05:59
    +1

    Да до хрена int


  1. vadimr
    12.08.2023 05:59
    -1

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

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

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


    1. SergeyProkhorenko Автор
      12.08.2023 05:59

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


    1. Akela_wolf
      12.08.2023 05:59
      +3

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

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


      1. vadimr
        12.08.2023 05:59
        +1

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

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


    1. tempart
      12.08.2023 05:59
      +9

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

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

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


    1. avost
      12.08.2023 05:59
      +4

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

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


    1. michael_v89
      12.08.2023 05:59
      +6

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


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


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


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


      1. vadimr
        12.08.2023 05:59
        +1

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


        1. michael_v89
          12.08.2023 05:59
          +2

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


          1. vadimr
            12.08.2023 05:59

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

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


            1. michael_v89
              12.08.2023 05:59

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


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

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


              1. vadimr
                12.08.2023 05:59

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

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

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


                1. michael_v89
                  12.08.2023 05:59

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

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


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

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


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

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


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

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


                  1. vadimr
                    12.08.2023 05:59
                    -1

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


      1. Akela_wolf
        12.08.2023 05:59
        +4

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

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


  1. funca
    12.08.2023 05:59
    +4

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


    1. Akela_wolf
      12.08.2023 05:59
      +3

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

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


  1. AntoineLarine
    12.08.2023 05:59
    +4

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


  1. astoulov
    12.08.2023 05:59
    -2

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


    1. SergeyProkhorenko Автор
      12.08.2023 05:59
      +1

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


      1. astoulov
        12.08.2023 05:59
        -1

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

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

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

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


        1. mayorovp
          12.08.2023 05:59

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


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

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


  1. Count_s
    12.08.2023 05:59

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