Какой тип данных лучше использовать для идентификаторов пользователей: строку, UUID или целое число?
Что проверяют
Умеете ли вы выбирать тип ключа под задачу, а не по привычке, и знаете ли, что происходит с индексом и с публичным API. Следом часто спрашивают: «А если ID торчит в URL?» или «Что будет при шардировании?».
Как отвечать
Разберите три варианта с ценой каждого:
- целое число: в PostgreSQL это
bigintсGENERATED ALWAYS AS IDENTITY. 8 байт, компактный B-tree, новые строки пишутся в конец индекса. Обычныйintegerкончается примерно на 2,1 млрд, и менять тип на живой таблице дорого, поэтому сразу берутbigint; в Django новые проекты создаются сBigAutoField. Минус: номер выдаёт база, а последовательные ID раскрывают объём данных и упрощают перебор чужих записей; - UUID: 16 байт в нативном типе
uuid, генерируется где угодно без обращения к базе, удобен в распределённых системах и при слиянии данных. UUIDv4 полностью случайный, вставки разлетаются по всему индексу, он раздувается и хуже держится в кэше. UUIDv7 из RFC 9562 начинается с метки времени и растёт почти монотонно. В Python 3.14 появилсяuuid.uuid7(), в PostgreSQL 18 функцияuuidv7(); - строка: оправдана, когда ID приходит извне, например
subот внешнего провайдера авторизации. Как первичный ключ хуже: переменная длина, правила сравнения и регистр.
Практичный компромисс: внутренний bigint для связей и джойнов, а наружу в API и URL отдельный uuid с уникальным индексом.
Где спотыкаются
Хранят UUID в varchar(36) вместо uuid. Это больше чем вдвое тяжелее 16 байт, индекс растёт, сравнение идёт по строкам. UUIDField в Django на PostgreSQL сам использует нативный тип.
Считают, что UUID в URL защищает данные. Он усложняет перебор, но проверку прав на объект не заменяет.
Отдают bigint в JSON числом. JavaScript точно представляет целые только до 2^53, и большие ID на фронтенде тихо искажаются, поэтому такие значения сериализуют строкой.