В мире баз данных идентификаторы имеют решающее значение для уникальной идентификации записей. Традиционно многие разработчики предпочитали автоматически увеличивающиеся целочисленные идентификаторы. Однако есть еще один вариант, который набирает популярность: универсально уникальные идентификаторы (UUID). В этой статье мы рассмотрим, почему UUID часто являются лучшим выбором по сравнению с автоматически увеличивающимися идентификаторами.

Везде уникально

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

Нет необходимости в центральном мастере

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

Отсутствие предсказуемости

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

Отсутствие необходимости в повторном обращении к базе данных

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

Лучше для распределенных систем

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

Подходит для автономной генерации данных

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

Совместимость с несколькими хранилищами данных

UUID не привязаны к конкретной технологии баз данных или производителю. Они могут использоваться в различных системах баз данных, включая базы данных SQL, NoSQL и хранилища документов. Такая гибкость позволяет выбрать подходящую базу данных для своего приложения и не беспокоиться о смене стратегии использования идентификаторов.

Заключение

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

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

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


  1. Shrk66
    11.09.2023 13:06
    +32

    А где же недостатки? Получается что кто числа использует - все бестолковые? Главный недостаток на мой взгляд что они очень уж жирные.


    1. YDR
      11.09.2023 13:06
      +7

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


      1. FanatPHP
        11.09.2023 13:06
        +3

        ULID вроде бы для этого


      1. AlexViolin
        11.09.2023 13:06
        +1

        Для  естественного упорядочения записей в таблицу вводится новая колонка CreatedDateTime это записи и индекс по этой колонке.


        1. ptr128
          11.09.2023 13:06

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


    1. breninsul
      11.09.2023 13:06
      +12

      главный недостаток - они не влазят в голову.

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

      Третий недостаток - в зависимости от метода генерации они таки могут повторятся, особенно в контейнерах.

      Четвертое - генерация ид централизованно обычно вообще не беда. Это очень быстрая операция. Да хоть отдельную посгрю под это завести, обычно это не доставит проблем.

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


      1. ptr128
        11.09.2023 13:06
        +1

        Шестое - в PostgreSQL gen_random_uuid() медленней bigserial в два раза даже на одном потоке. А так как доступ к энтропии производится через глобальную блокировку, то на нескольких потоках может еще медленней оказаться.


      1. SergeyProkhorenko
        11.09.2023 13:06
        +1

        Все эти аргументы являются ложными для версии UUIDv7, устраняющей недостатки прежних версий. Читайте стандарт: https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/ Насчет первого аргумента: пользуйтесь SQL, поиском и копипастой


        1. Helldar
          11.09.2023 13:06
          +1

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

          use Illuminate\Support\Str;
          
          class Uuid
          {
              public static function generate(Model|string $model): string
              {
                  do {
                      $uuid = Str::uuid();
                  } while ($model::whereKey($uuid)->exists());
          
                  return $uuid;
              }
          }

          И, на практике с таблицей примерно в 2к записей бывали случаи когда уникальный идентификатор подбирался за 2, а то и за 3 прохода. В основном это случалось в момент массовой обработки данных по 500-1000 элементов.


        1. ptr128
          11.09.2023 13:06
          +2

          Во-первых, такое ощущение, что Вы не читали, что Вам пишут. Главный недостаток остается каким был. Хоть v7 - все равно в голову они влазят. Четвертое и пятое - остается в силе для любых версий UUID. Как избежать третьего в контейнерах с одинаковой энтропией, даже для v7 - не понимаю. Да, с дублированием я встречался буквально разы, но все же встречался. И стандарт v7, помогая решить проблему индексации в БД, вероятность дублирования только повышает.


      1. Horocek
        11.09.2023 13:06

        Да банально если ты используешь uuid ты лишен бинарного поиска по идентификатору, а в больших таблицах это ощутимо


        1. BASic_37
          11.09.2023 13:06
          +1

          Зачем вам бинарный поиск, почему вы его вдруг лишились (а он был?) и почему именно он ощутим в больших таблицах?


          1. Horocek
            11.09.2023 13:06

            Конкретно у себя на работе его не пользую, да и вряд ли кто то прям им пользуется, но вроде оптимизатор запросов в БД под капотом им пользуется, но это не точно)


            1. ptr128
              11.09.2023 13:06
              +1

              В БД оптимизатор или идет по индексу, обычно, BTree, или строит хеш-таблицу.

              И UUID до v7 влияло не столько на поиск в индексе, сколько на перестроение этого индекса при добавлении новых записей. Так как намного дешевле перестраивать индекс при добавлении монотонно возрастающих значений, чем случайных.


              1. Horocek
                11.09.2023 13:06

                Понял, спасибо!


    1. Intiligent
      11.09.2023 13:06

      ещё наверное не получится от ротировать по uuid как например по id чтобы получить последние записи


  1. VVitaly
    11.09.2023 13:06
    +11

    :-) У вас в базе максимум миллиард объектов и тысяча миллиардов записей действий с ними.... А уж индексов их включающих... Скажем так "много".
    Я не говорю даже о объеме информации хранения (она разная особенно для UTF8 VARCHAR). А вот скорость работы (и объем) с индексами по NUMBER и VARCHAR полям отличается значительно. Причем "специальный" UUID data type на индексах производительностью совсем "не блещет"...


    1. FanatPHP
      11.09.2023 13:06
      +2

      Но мы же говорим не про UTF8 VARCHAR, а про хранение 130 бит информации. А это всего в два раза больше чем bigint...


      1. VVitaly
        11.09.2023 13:06

        :-) Теоретизировать - это хорошо... Вы на практике в БД попробуйте и сравните.... :-)


      1. VVitaly
        11.09.2023 13:06

        :-)
        Oracle - Number having precision p and scale s. The precision p can range from 1 to 38. The scale s can range from -84 to 127. Both precision and scale are in decimal digits. A NUMBER value requires from 1 to 22 bytes.
        PostgeSQL - bigserial 8 bytes large autoincrementing integer1 to 9223372036854775807


  1. Naf2000
    11.09.2023 13:06
    +8

    Насколько хорошо индексирует ваша СУБД UUID?

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

    "UUID не привязаны к конкретной технологии баз данных или производителю" - ну это как сказать, где-то есть тип данных UUID, где-то придется строкой хранить. Опять же как затратно индексируется все это. Будет ли на разных СУБД одинаково? С числами таких проблем не будет, думаю

    "Везде уникально" и "Отсутствие предсказуемости" - зависит от умения готовить UUID. Опять же отсутствие предсказуемости вредит индексации.


    1. andreymal
      11.09.2023 13:06
      +2

      отсутствие предсказуемости вредит индексации

      Это вроде как решили в UUIDv7


    1. arylkov
      11.09.2023 13:06

      Есть такие минусы.

      Операции сортировки и between и им подобные утрачивают смысл.

      В операции join операции с целым числом эффективнее чем со строкой. Можно найти примеры где измеряют производительность. На большом объёме разница существенна.


  1. andreymal
    11.09.2023 13:06

    Они могут использоваться в различных системах баз данных

    Вот пример из моего личного опыта — в Manticore нельзя, максимум 64 бита


    1. breninsul
      11.09.2023 13:06
      +1

      В ElasticSearch/OpenSearch тоже. Но там текст бахать, это всё-таки обычно не основная БД


  1. vagon333
    11.09.2023 13:06
    +5

    Разным задачам разные ID.

    Если ID для обмена данными с внешними системами - да UUID обязателен.
    Если ID для обращения к записи из веб-клиента - да UUID обязателен.

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

    А производительность (MSSQL) по сравнению с INT - это disaster.


    1. breninsul
      11.09.2023 13:06

      почему uuid обязателен для апмшки и внешн. систем? Логический ключ, если он есть, обычно, лучше


      1. vagon333
        11.09.2023 13:06
        +1

        Логический ключ, если он есть, обычно, лучше

        Я не совсем в курсе, что есть "логический ключ", но если комплексный ключ из полей записи, то могут подвернуться изменяемые данные. Постоянные ID лучше.


        1. breninsul
          11.09.2023 13:06

          да, зависит от того, могут ли подвергаться изменению.

          Например, NFT токен как логический ключ имеет id внутри контракта и сам контракт (адрес и сеть) (tokenId:uint256,(chainId:int,address:20bytes)). Ничего не может измениться, введение суррогатного ид только запутает.

          А вот с человеком/профилем, например, такое не выйдет, да. Впрочем, нет никакого кошмара передать int64 как id в апи. Telegram ещё не умер от такого. Меньше новых сущностей не имеющих смысловой нагрузки - меньше путаницы


    1. BugM
      11.09.2023 13:06

      Если ID для обмена данными с внешними системами - да UUID обязателен.Если ID для обращения к записи из веб-клиента - да UUID обязателен.

      На самом деле нет.

      Ну узнает весь мир ваши внутренние ID всяких сущностей. И что? У вас же все закрыто нормальными правами и перебор чего-то интересного невозможен? Верно?

      Часто встречаемое исключение когда надо что-то ограниченного доступа сделать через ссылку. Но при этом если эта информация утечет в целом ничего страшного. Местоположение вашего курьера на карте, например. Ссылка в СМС, получателю про которого мы ничего не знаем. Или трекинг номер посылки. Прямо открывать всё всем не хочется. Но утечет и ладно, потерь никаких.


      1. vagon333
        11.09.2023 13:06
        +1

        Ну узнает весь мир ваши внутренние ID всяких сущностей. И что?

        Чтение данных методом перебора ID.
        Сам так делал, и не раз.
        Да, и сейчас синхронно начитываем крупную базу, а пришлось бы ручками.
        А разрабам запрещаю это непотребство в нашей работе. :)


        1. BugM
          11.09.2023 13:06

          Я буквально чуть ниже про это написал. Вы точно прочитали полностью мое сообщение?


          1. vagon333
            11.09.2023 13:06

            Вы спросили, я ответил.
            Даже если вы сами ответили на вопрос - вы спросили, и я посчитал нужным ответить. :)


        1. breninsul
          11.09.2023 13:06
          +2

          если запрос авторизован, то проблем 0. Получит 401/403.

          Если запрос открытый - да пусть перебирает на здоровье, паблик апи же. Прикрутить капчу v3 в конце концов.

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


  1. miksoft
    11.09.2023 13:06
    +2

    автоматически увеличивающиеся идентификаторы уникальны только в контексте одной таблицы базы данных

    Вообще-то, нет. Часто встречаю, что в последних двух или трех цифрах закодирован источник автоматически увеличивающегося идентификатора. Соответственно, инкремент происходит с шагом 100 или 1000.


    1. miksoft
      11.09.2023 13:06
      +1

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


    1. MyraJKee
      11.09.2023 13:06
      +1

      Интересно, почему последних, а не первых?


      1. pae174
        11.09.2023 13:06

        Потому что разницу между 1000000000123 и 10000000000123 вы заметите не сразу а разницу между 123001 и 123010 - мгновенно.


      1. miksoft
        11.09.2023 13:06

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


      1. JGooLaaR
        11.09.2023 13:06

        Инкремент не налезет на код в таком случае.


  1. klimkinMD
    11.09.2023 13:06

    Работал как-то для крупного банка. Так у них на одного сотрудника 5(!) этих ваших UUID. А чё, -- халява! Доходило до того, что приходилось UUID'ы связывать через e-mal'ы.


    1. miksoft
      11.09.2023 13:06
      +1

      Это немного не из этой области. С автоинкрементом может быть (и часто бывает) такая же ситуация.


  1. SergeyProkhorenko
    11.09.2023 13:06

    Странно выглядит статья про UUID без ссылки на новый стандарт UUID, который уже на последней стадии утверждения. Введенные новым стандартом UUID седьмой версии (UUIDv7) полностью устраняют очень серьезные недостатки прежних версий UUID. В первую очередь, максимально увеличивается скорость индексирования и вставки новых записей, а также поиска записей в БД. Более полно достоинства UUIDv7 изложены в статье "Встречайте UUID нового поколения для ключей высоконагруженных систем" и многочисленных комментариях к ней.

    Автор комментируемой статьи безусловно прав, отмечая достоинства UUID. Но к ним необходимо добавить следующее:
    1. Неотъемлемые недостатки автоинкремента (относительно указанных в статье достоинств UUID) заставляют проектировщиков БД использовать составные бизнес-ключи, которые многократно длиннее, чем UUID, сильно замедляют соединение таблиц и порождают большое количество трудноуловимых и трудноустранимых дефектов данных. Кроме того, составные ключи не подходят для правильно спроектированных больших хранилищ данных (по методологиям Data Vault или Anchor Modeling).
    2. Автоинкремент, в отличие от UUID, не подходит для интеграции данных из разных информационных систем и для поиска причин дефектов данных, так как он требует замены ключа при передаче данных из одной системы в другую.
    3. В условиях отсутствия подробной документации (а согласно Манифесту господствующей ныне идеологии Agile, решение проблемы клиента важнее проработанной до мелочей документации), отсутствия метаданных и констрейнтов (их не модно создавать, как и комментарии в программном коде) системные аналитики вынуждены изучать фактическую структуру БД по значениям в ключевых полях. При этом UUID дают абсолютную уверенность в выводах, а вот автоинкремент порождает массовые случайные совпадения ключей.

    Поэтому "экономия на спичках" в виде выбора автоинкремента для сокращения длины идентификатора оборачивается огромными финансовыми потерями, хотя, конечно, выглядит как полезная работа.


    1. miksoft
      11.09.2023 13:06
      +1

      Автоинкремент ... требует замены ключа при передаче данных из одной системы в другую.

      Хм, а зачем?

      Мы как-то без замены передаем и ничего, работает...


      1. breninsul
        11.09.2023 13:06

        Сказали, что надо)))

        иначе внешняя система узнает внутренний ид иииии... произойдет ужас по мнению автора


    1. BugM
      11.09.2023 13:06
      +2

      Поэтому "экономия на спичках" в виде выбора автоинкремента

      Это экономия сотен и тысяч часов дебага. Когда очередной разработчик на глаз перепутал пару UUID и сделал что-то не то.


      1. breninsul
        11.09.2023 13:06
        +3

        ахах, жиза.

        Я обычно просто забываю чё искали при переключении окна. Но это ладно, их еще (-) и экранировать при грепании надо


      1. NoGotnu
        11.09.2023 13:06
        +2

        Какой-то странный довод для аргументации архитектуры системы/хранилища. Может разработчик всё-таки не будет что-то там делать "на глаз", "на память" и т.д. и т.п. Или может он просто хреновый разработчик?


        1. BugM
          11.09.2023 13:06

          В идеальном мире не будет. В реальном мире копаются в данных и что-то там ищут/меняют регулярно.


  1. am-habr
    11.09.2023 13:06

    Комментарии интереснее оказались, чем статья. Увлекаясь преимуществами, забыли о недостатках.


  1. jakobz
    11.09.2023 13:06

    Мы как-то на наших задачах смотрели - uuid был в два раза медленнее на селектах. Ну оно и понятно - он в два раза больше, а в обычной такой OLTP-базе у тебя половина колонок - это id (ведь кроме PK есть куча FK). А размер строки - влияет буквально на все.

    Т.к. БД обычно - узкое место, как-то стрёмно прям 2х терять от входа.

    Снаружи системы - во внешних API и кафках, все равно обычно мешанина - где guid, где long. Для людей вообще email могут быть. И мы все равно об отдельную табличку маппим с внешних id (которые для универсальности вообще varchar) на внутренние.

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

    В общем пока пихать uuid везде стремно, хотя плюсы понятны и желанны.


    1. SergeyProkhorenko
      11.09.2023 13:06

      Дождитесь официальной реализации UUIDv7 для Вашей СУБД. Для PostgreSQL ждать осталось недолго. Она обеспечит производительность почти как у автоинкремента. Сейчас уже есть самодельные реализации UUIDv7, но они хуже, так как не обеспечивают всех преимуществ, предусмотренных новым стандартом. Если торопитесь, то можете их попробовать.


      1. ptr128
        11.09.2023 13:06

        Проблему периодического получения новых записей из таблиц UUID никакой версии не решает. Все равно приходится использовать bigserial.


  1. manyakRus
    11.09.2023 13:06

    У нас был ИД UUID, пришлось сделать ещё одно поле инкрементный int чтоб можно было выбирать первые 1000 строк, с UUID такого не получится - никакой сортировки, при добавлении записи новая строка залезет внутрь первых 1000 строк - куда не надо


  1. ggo
    11.09.2023 13:06
    +3

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


  1. KivApple
    11.09.2023 13:06
    +4

    • UUIDv4 плохо дружат с индексами многих БД. В смысле индексы от них толстеют и начинают тормозить - B-деревья не любят случайные данные.

    • UUID весит в 2 раза больше BIGINT и если используется как первичный ключ, то все индексы станут жирнее (потому что все индексы включают в себя копию первичного ключа). Жирные индексы не влезают в кеши и тормозят.

    • Из-за плохой читаемости UUID, тяжело отлаживать систему.


  1. seriych
    11.09.2023 13:06

    snowflake id или sonyflake id почти всегда лучше, ибо 8 байт и монотонность. Возможно, хуже там где "Отсутствие предсказуемости" - это серьезный фактор. Ну или у вас ну ооочень много юников.


    1. SergeyProkhorenko
      11.09.2023 13:06

      snowflake id и sonyflake id хороши в простом и идеальном мире. Разработчики UUIDv7 внимательнейшим образом изучили и snowflake id, и sonyflake id. Но в условиях царящего в IT бардака (включая совпадения MAC-адресов) UUIDv7 - это единственный абсолютно надежный вариант. Он позволяет спокойно проектировать и интегрировать сколь угодно сложные критические высоконагруженные информационные системы, не обкладываясь со всех сторон "страховочными механизмами"


  1. petro__vich
    11.09.2023 13:06

    1 размер ключей и индексов неоправдано разпухнет

    2 базы mssql для pk строит кластерные индексы, и при вставке в середину, перестраивает его, а это постояная работа с диском

    3 если отображение должно быть в порядке добавления, то придется добавлять дату, потому что order by id невозможен


  1. LKU
    11.09.2023 13:06

    Просто представьте, что вы работаете в поддержке и вам пользователь выставляет тикет со скриншотом проблемного документа. Сравните затраты на перебивание числового 10 или даже 8 значного номера по сравнению с 16 или 32 шестнадцатеричнвми числами.

    Что важно, эта проблема с UUID проявляется уже при первых сотнях записей в таблице, а не когда у вас там 10млрд строк.

    "Программисты - это люди, которые решают непонятные вам проблемы непонятными вам способами"


    1. ptr128
      11.09.2023 13:06

      пользователь выставляет тикет со скриншотом проблемного документа

      dpScreenOCR не пробовали?.


      1. LKU
        11.09.2023 13:06

        Не пробовал и не был в курсе существования, как и коллеги из разных компаний, с которыми взаимодействовал. При попытке запустить установщик, на него ругается win defender.

        Мое утверждение простое: перечисленные плюсы UUID начинают работать на больших объемах и высоких нагрузках, до которых 99.9 таблиц в БД обычно никогда не дотягивают.

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