В мире баз данных идентификаторы имеют решающее значение для уникальной идентификации записей. Традиционно многие разработчики предпочитали автоматически увеличивающиеся целочисленные идентификаторы. Однако есть еще один вариант, который набирает популярность: универсально уникальные идентификаторы (UUID). В этой статье мы рассмотрим, почему UUID часто являются лучшим выбором по сравнению с автоматически увеличивающимися идентификаторами.
Везде уникально
Одним из основных преимуществ UUID является их глобальная уникальность. UUID разработаны таким образом, чтобы быть универсально уникальными в пространстве и времени. Это означает, что даже если у вас есть несколько баз данных, распределенных систем или разных объектов, генерирующих идентификаторы, вероятность столкновения практически равна нулю. Напротив, автоматически увеличивающиеся идентификаторы уникальны только в контексте одной таблицы базы данных, что может привести к конфликтам при объединении данных или работе с распределенными системами.
Нет необходимости в центральном мастере
Автоинкрементные идентификаторы обычно требуют централизованного управления генерацией уникальных идентификаторов с помощью главной базы данных. Такой централизованный подход может стать узким местом при расширении приложения. С другой стороны, UUID могут генерироваться независимо друг от друга различными подразделениями или системами без необходимости координации. Такая децентрализация особенно выгодна в распределенных и микросервисных архитектурах.
Отсутствие предсказуемости
Автоинкрементные идентификаторы часто предсказуемы, так как они следуют последовательно. Такая предсказуемость может представлять угрозу безопасности, поскольку злоумышленники могут попытаться угадать или вывести идентификаторы других записей. UUID, напротив, проектируются как случайные и непредсказуемые, что делает их более безопасными по умолчанию.
Отсутствие необходимости в повторном обращении к базе данных
Генерация автоинкрементирующихся идентификаторов часто требует обращения к базе данных для получения следующего доступного значения. Это может привести к задержкам и снижению производительности, особенно в сценариях с высокой скоростью обмена данными. UUID могут генерироваться полностью на стороне приложения, что снижает необходимость во взаимодействии с базой данных и повышает общую производительность.
Лучше для распределенных систем
В распределенных системах использование автоинкрементных идентификаторов может быть проблематичным, поскольку каждый узел базы данных может иметь свой собственный набор инкрементных значений, что может привести к коллизиям при объединении или синхронизации данных. UUID, будучи глобально уникальными, лучше подходят для распределенных сред, поскольку они могут генерироваться независимо на разных узлах без риска возникновения конфликтов.
Подходит для автономной генерации данных
UUID также хорошо подходят для генерации идентификаторов в автономном режиме или при отсутствии связи. Например, мобильные приложения могут генерировать UUID для новых записей в автономном режиме и затем синхронизировать их с сервером. Автоинкрементные идентификаторы в таких ситуациях нецелесообразны, поскольку они зависят от подключения к базе данных.
Совместимость с несколькими хранилищами данных
UUID не привязаны к конкретной технологии баз данных или производителю. Они могут использоваться в различных системах баз данных, включая базы данных SQL, NoSQL и хранилища документов. Такая гибкость позволяет выбрать подходящую базу данных для своего приложения и не беспокоиться о смене стратегии использования идентификаторов.
Заключение
В то время как автоинкрементные идентификаторы были распространенным выбором для идентификаторов баз данных, UUID обладают рядом преимуществ, которые делают их привлекательным вариантом, особенно при разработке современного программного обеспечения. Глобальная уникальность, децентрализация, безопасность и совместимость с различными хранилищами данных делают их ценным инструментом для разработчиков, работающих с распределенными системами и не только. При выборе стратегии идентификации для своего следующего проекта серьезно рассмотрите возможность использования UUID для получения более масштабируемого, безопасного и гибкого решения.
Надеюсь, моя первая публикация окажется полезной для вашего следующего продукта. Спасибо за прочтение этой статьи!
Комментарии (62)
VVitaly
11.09.2023 13:06+11:-) У вас в базе максимум миллиард объектов и тысяча миллиардов записей действий с ними.... А уж индексов их включающих... Скажем так "много".
Я не говорю даже о объеме информации хранения (она разная особенно для UTF8 VARCHAR). А вот скорость работы (и объем) с индексами по NUMBER и VARCHAR полям отличается значительно. Причем "специальный" UUID data type на индексах производительностью совсем "не блещет"...FanatPHP
11.09.2023 13:06+2Но мы же говорим не про UTF8 VARCHAR, а про хранение 130 бит информации. А это всего в два раза больше чем bigint...
VVitaly
11.09.2023 13:06:-) Теоретизировать - это хорошо... Вы на практике в БД попробуйте и сравните.... :-)
VVitaly
11.09.2023 13:06:-)
Oracle - Number having precisionp
and scales
. The precisionp
can range from 1 to 38. The scales
can range from -84 to 127. Both precision and scale are in decimal digits. ANUMBER
value requires from 1 to 22 bytes.
PostgeSQL - bigserial 8 bytes large autoincrementing integer1 to 9223372036854775807
Naf2000
11.09.2023 13:06+8Насколько хорошо индексирует ваша СУБД UUID?
По факту приведены одни и те же вопросы с разных сторон просто. "Нет необходимости в центральном мастере", "Лучше для распределенных систем" и "Подходит для автономной генерации данных" - это одно и тоже.
"UUID не привязаны к конкретной технологии баз данных или производителю" - ну это как сказать, где-то есть тип данных UUID, где-то придется строкой хранить. Опять же как затратно индексируется все это. Будет ли на разных СУБД одинаково? С числами таких проблем не будет, думаю
"Везде уникально" и "Отсутствие предсказуемости" - зависит от умения готовить UUID. Опять же отсутствие предсказуемости вредит индексации.
andreymal
11.09.2023 13:06+2отсутствие предсказуемости вредит индексации
Это вроде как решили в UUIDv7
arylkov
11.09.2023 13:06Есть такие минусы.
Операции сортировки и between и им подобные утрачивают смысл.
В операции join операции с целым числом эффективнее чем со строкой. Можно найти примеры где измеряют производительность. На большом объёме разница существенна.
vagon333
11.09.2023 13:06+5Разным задачам разные ID.
Если ID для обмена данными с внешними системами - да UUID обязателен.
Если ID для обращения к записи из веб-клиента - да UUID обязателен.Если для RI в рамках базы - UUID избыточен и сложен для траблшутинга. INT проще запомнить когда ковыряешься в связях.
Если для работы с базой в коде - UUID избыточен и сложнее в работе чем INT.А производительность (MSSQL) по сравнению с INT - это disaster.
breninsul
11.09.2023 13:06почему uuid обязателен для апмшки и внешн. систем? Логический ключ, если он есть, обычно, лучше
vagon333
11.09.2023 13:06+1Логический ключ, если он есть, обычно, лучше
Я не совсем в курсе, что есть "логический ключ", но если комплексный ключ из полей записи, то могут подвернуться изменяемые данные. Постоянные ID лучше.
breninsul
11.09.2023 13:06да, зависит от того, могут ли подвергаться изменению.
Например, NFT токен как логический ключ имеет id внутри контракта и сам контракт (адрес и сеть) (tokenId:uint256,(chainId:int,address:20bytes)). Ничего не может измениться, введение суррогатного ид только запутает.
А вот с человеком/профилем, например, такое не выйдет, да. Впрочем, нет никакого кошмара передать int64 как id в апи. Telegram ещё не умер от такого. Меньше новых сущностей не имеющих смысловой нагрузки - меньше путаницы
BugM
11.09.2023 13:06Если ID для обмена данными с внешними системами - да UUID обязателен.Если ID для обращения к записи из веб-клиента - да UUID обязателен.
На самом деле нет.
Ну узнает весь мир ваши внутренние ID всяких сущностей. И что? У вас же все закрыто нормальными правами и перебор чего-то интересного невозможен? Верно?
Часто встречаемое исключение когда надо что-то ограниченного доступа сделать через ссылку. Но при этом если эта информация утечет в целом ничего страшного. Местоположение вашего курьера на карте, например. Ссылка в СМС, получателю про которого мы ничего не знаем. Или трекинг номер посылки. Прямо открывать всё всем не хочется. Но утечет и ладно, потерь никаких.
vagon333
11.09.2023 13:06+1Ну узнает весь мир ваши внутренние ID всяких сущностей. И что?
Чтение данных методом перебора ID.
Сам так делал, и не раз.
Да, и сейчас синхронно начитываем крупную базу, а пришлось бы ручками.
А разрабам запрещаю это непотребство в нашей работе. :)breninsul
11.09.2023 13:06+2если запрос авторизован, то проблем 0. Получит 401/403.
Если запрос открытый - да пусть перебирает на здоровье, паблик апи же. Прикрутить капчу v3 в конце концов.
Если всякие одноразовые ссылки - тогда уж да. Но сколько сущностей в приложении оно распространяется? В большинстве случаев 0, в ином случае мизерная часть энивэй.
miksoft
11.09.2023 13:06+2автоматически увеличивающиеся идентификаторы уникальны только в контексте одной таблицы базы данных
Вообще-то, нет. Часто встречаю, что в последних двух или трех цифрах закодирован источник автоматически увеличивающегося идентификатора. Соответственно, инкремент происходит с шагом 100 или 1000.
miksoft
11.09.2023 13:06+1И, кстати, один источник автоматически увеличивающихся идентификаторов может использоваться для нескольких таблиц. И не всегда одной базы данных.
MyraJKee
11.09.2023 13:06+1Интересно, почему последних, а не первых?
pae174
11.09.2023 13:06Потому что разницу между 1000000000123 и 10000000000123 вы заметите не сразу а разницу между 123001 и 123010 - мгновенно.
miksoft
11.09.2023 13:06Так удобнее. Например, легко вычленять номер источника независимо от того, сколько ему знаков требуется на основную часть, в т.ч. при записи в столбик, т.к. выравнивание чисел обычно вправо. Запись идентификаторов короче и читабельнее.
klimkinMD
11.09.2023 13:06Работал как-то для крупного банка. Так у них на одного сотрудника 5(!) этих ваших UUID. А чё, -- халява! Доходило до того, что приходилось UUID'ы связывать через e-mal'ы.
miksoft
11.09.2023 13:06+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 дают абсолютную уверенность в выводах, а вот автоинкремент порождает массовые случайные совпадения ключей.
Поэтому "экономия на спичках" в виде выбора автоинкремента для сокращения длины идентификатора оборачивается огромными финансовыми потерями, хотя, конечно, выглядит как полезная работа.miksoft
11.09.2023 13:06+1Автоинкремент ... требует замены ключа при передаче данных из одной системы в другую.
Хм, а зачем?
Мы как-то без замены передаем и ничего, работает...
breninsul
11.09.2023 13:06Сказали, что надо)))
иначе внешняя система узнает внутренний ид иииии... произойдет ужас по мнению автора
BugM
11.09.2023 13:06+2Поэтому "экономия на спичках" в виде выбора автоинкремента
Это экономия сотен и тысяч часов дебага. Когда очередной разработчик на глаз перепутал пару UUID и сделал что-то не то.
breninsul
11.09.2023 13:06+3ахах, жиза.
Я обычно просто забываю чё искали при переключении окна. Но это ладно, их еще (-) и экранировать при грепании надо
NoGotnu
11.09.2023 13:06+2Какой-то странный довод для аргументации архитектуры системы/хранилища. Может разработчик всё-таки не будет что-то там делать "на глаз", "на память" и т.д. и т.п. Или может он просто хреновый разработчик?
BugM
11.09.2023 13:06В идеальном мире не будет. В реальном мире копаются в данных и что-то там ищут/меняют регулярно.
am-habr
11.09.2023 13:06Комментарии интереснее оказались, чем статья. Увлекаясь преимуществами, забыли о недостатках.
jakobz
11.09.2023 13:06Мы как-то на наших задачах смотрели - uuid был в два раза медленнее на селектах. Ну оно и понятно - он в два раза больше, а в обычной такой OLTP-базе у тебя половина колонок - это id (ведь кроме PK есть куча FK). А размер строки - влияет буквально на все.
Т.к. БД обычно - узкое место, как-то стрёмно прям 2х терять от входа.
Снаружи системы - во внешних API и кафках, все равно обычно мешанина - где guid, где long. Для людей вообще email могут быть. И мы все равно об отдельную табличку маппим с внешних id (которые для универсальности вообще varchar) на внутренние.
Сейчас погуглил быстро - особо не нашел хороших статей с тестами на производительность, но примерно такие результаты гуглятся.
В общем пока пихать uuid везде стремно, хотя плюсы понятны и желанны.
SergeyProkhorenko
11.09.2023 13:06Дождитесь официальной реализации UUIDv7 для Вашей СУБД. Для PostgreSQL ждать осталось недолго. Она обеспечит производительность почти как у автоинкремента. Сейчас уже есть самодельные реализации UUIDv7, но они хуже, так как не обеспечивают всех преимуществ, предусмотренных новым стандартом. Если торопитесь, то можете их попробовать.
ptr128
11.09.2023 13:06Проблему периодического получения новых записей из таблиц UUID никакой версии не решает. Все равно приходится использовать bigserial.
manyakRus
11.09.2023 13:06У нас был ИД UUID, пришлось сделать ещё одно поле инкрементный int чтоб можно было выбирать первые 1000 строк, с UUID такого не получится - никакой сортировки, при добавлении записи новая строка залезет внутрь первых 1000 строк - куда не надо
ggo
11.09.2023 13:06+3Хехе, краткий вывод практиков из комментариев (и я к ним присоединяюсь): не используйте UUID, кроме случаев, где без UUID по какой-то причине нельзя.
KivApple
11.09.2023 13:06+4UUIDv4 плохо дружат с индексами многих БД. В смысле индексы от них толстеют и начинают тормозить - B-деревья не любят случайные данные.
UUID весит в 2 раза больше BIGINT и если используется как первичный ключ, то все индексы станут жирнее (потому что все индексы включают в себя копию первичного ключа). Жирные индексы не влезают в кеши и тормозят.
Из-за плохой читаемости UUID, тяжело отлаживать систему.
seriych
11.09.2023 13:06snowflake id или sonyflake id почти всегда лучше, ибо 8 байт и монотонность. Возможно, хуже там где "Отсутствие предсказуемости" - это серьезный фактор. Ну или у вас ну ооочень много юников.
SergeyProkhorenko
11.09.2023 13:06snowflake id и sonyflake id хороши в простом и идеальном мире. Разработчики UUIDv7 внимательнейшим образом изучили и snowflake id, и sonyflake id. Но в условиях царящего в IT бардака (включая совпадения MAC-адресов) UUIDv7 - это единственный абсолютно надежный вариант. Он позволяет спокойно проектировать и интегрировать сколь угодно сложные критические высоконагруженные информационные системы, не обкладываясь со всех сторон "страховочными механизмами"
petro__vich
11.09.2023 13:061 размер ключей и индексов неоправдано разпухнет
2 базы mssql для pk строит кластерные индексы, и при вставке в середину, перестраивает его, а это постояная работа с диском
3 если отображение должно быть в порядке добавления, то придется добавлять дату, потому что order by id невозможен
LKU
11.09.2023 13:06Просто представьте, что вы работаете в поддержке и вам пользователь выставляет тикет со скриншотом проблемного документа. Сравните затраты на перебивание числового 10 или даже 8 значного номера по сравнению с 16 или 32 шестнадцатеричнвми числами.
Что важно, эта проблема с UUID проявляется уже при первых сотнях записей в таблице, а не когда у вас там 10млрд строк.
"Программисты - это люди, которые решают непонятные вам проблемы непонятными вам способами"
ptr128
11.09.2023 13:06пользователь выставляет тикет со скриншотом проблемного документа
dpScreenOCR не пробовали?.
LKU
11.09.2023 13:06Не пробовал и не был в курсе существования, как и коллеги из разных компаний, с которыми взаимодействовал. При попытке запустить установщик, на него ругается win defender.
Мое утверждение простое: перечисленные плюсы UUID начинают работать на больших объемах и высоких нагрузках, до которых 99.9 таблиц в БД обычно никогда не дотягивают.
А вот крайне существенный недостаток начинает портить жизнь немедленно и заключается он в том что UUID обычный человек не может в своей голове запомнить и сравнить с другим. Помогает только копипаст и формулы в excel.
Shrk66
А где же недостатки? Получается что кто числа использует - все бестолковые? Главный недостаток на мой взгляд что они очень уж жирные.
YDR
еще возможный недостаток - что не создают некоторого естественного упорядочения записей.
FanatPHP
ULID вроде бы для этого
AlexViolin
Для естественного упорядочения записей в таблицу вводится новая колонка CreatedDateTime это записи и индекс по этой колонке.
ptr128
Несмотря на полезность такого поля, это все же костыль, так как может совпадать у нескольких строк. Значит детерминированный порядок выборки записей обеспечить не может. Иногда это важно.
breninsul
главный недостаток - они не влазят в голову.
Второй главный - выходит из плюсов. Они не предсказуемы, что делает возможным, и даже "нормальным" появление дублей. Можно "починить" генерацией их как хэша от логического ключа. Но это будет не uuid по спецификации, а просто тип данных uuid, если таковой поддерживается бд.
Третий недостаток - в зависимости от метода генерации они таки могут повторятся, особенно в контейнерах.
Четвертое - генерация ид централизованно обычно вообще не беда. Это очень быстрая операция. Да хоть отдельную посгрю под это завести, обычно это не доставит проблем.
Пятое - мультимастер на запись не простая штука, задачу византийских генералов никто не отменял. В большинстве случаев консистентность гораздо важнее.
ptr128
Шестое - в PostgreSQL gen_random_uuid() медленней bigserial в два раза даже на одном потоке. А так как доступ к энтропии производится через глобальную блокировку, то на нескольких потоках может еще медленней оказаться.
SergeyProkhorenko
Все эти аргументы являются ложными для версии UUIDv7, устраняющей недостатки прежних версий. Читайте стандарт: https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/ Насчет первого аргумента: пользуйтесь SQL, поиском и копипастой
Helldar
Во многих проектах что я видел, чаще всего используется uuid4. На практике в одном маленьком проекте также имел проблему с дубликатами, вследствие чего приходилось писать костыли в виде:
И, на практике с таблицей примерно в 2к записей бывали случаи когда уникальный идентификатор подбирался за 2, а то и за 3 прохода. В основном это случалось в момент массовой обработки данных по 500-1000 элементов.
ptr128
Во-первых, такое ощущение, что Вы не читали, что Вам пишут. Главный недостаток остается каким был. Хоть v7 - все равно в голову они влазят. Четвертое и пятое - остается в силе для любых версий UUID. Как избежать третьего в контейнерах с одинаковой энтропией, даже для v7 - не понимаю. Да, с дублированием я встречался буквально разы, но все же встречался. И стандарт v7, помогая решить проблему индексации в БД, вероятность дублирования только повышает.
Horocek
Да банально если ты используешь uuid ты лишен бинарного поиска по идентификатору, а в больших таблицах это ощутимо
BASic_37
Зачем вам бинарный поиск, почему вы его вдруг лишились (а он был?) и почему именно он ощутим в больших таблицах?
Horocek
Конкретно у себя на работе его не пользую, да и вряд ли кто то прям им пользуется, но вроде оптимизатор запросов в БД под капотом им пользуется, но это не точно)
ptr128
В БД оптимизатор или идет по индексу, обычно, BTree, или строит хеш-таблицу.
И UUID до v7 влияло не столько на поиск в индексе, сколько на перестроение этого индекса при добавлении новых записей. Так как намного дешевле перестраивать индекс при добавлении монотонно возрастающих значений, чем случайных.
Horocek
Понял, спасибо!
Intiligent
ещё наверное не получится от ротировать по uuid как например по id чтобы получить последние записи