Два способа сделать первичный ключ
Большинству таблиц нужен уникальный ключ для каждой строки. Два распространённых выбора — автоинкрементное целое базы (1, 2, 3, ...) и UUID. Целое маленькое и естественно упорядоченное; UUID можно сгенерировать где угодно, кем угодно, без координации. Что лучше, зависит от того, что вы цените, и у ответа есть измерение производительности, которое легко упустить.
Почему команды берут UUID
- Никакой координации. Любой сервис, клиент или офлайн-устройство может отчеканить UUID, который не столкнётся, так что вам не нужна центральная последовательность или путь туда-обратно к базе, чтобы получить id. Это бесценно в распределённых системах и для генерации id до сохранения строки.
- Неугадываемость. Последовательные целочисленные id утекают информацию (сколько заказов существует и id следующего) и позволяют атакующему перебирать записи инкрементацией. Случайный UUID не раскрывает ничего из этого.
- Дружелюбие к слиянию. Данные из разных систем объединяются без коллизий id.
Цена — размер, 16 байт против 4 или 8 у целого, что важно, потому что первичный ключ копируется в каждый вторичный индекс и каждый внешний ключ, ссылающийся на строку.
Скрытая цена случайных ключей
Есть более тонкая, более важная цена. Базы хранят строки в индексе (обычно B-дереве), упорядоченном по первичному ключу. С последовательным ключом каждая новая строка добавляется в конец индекса, так что активные страницы остаются в памяти, а записи дешевы. Со случайным UUID v4 каждая вставка попадает в произвольную позицию. База вынуждена читать, изменять и записывать страницы, разбросанные по всему индексу, вызывая расщепления страниц, фрагментацию и куда больше промахов кеша. На большой, активно записываемой таблице случайные ключи могут измеримо замедлить вставки и раздуть индекс, и всё это без какой-либо ошибки, на которую можно указать.
Почему v7 меняет расчёт
Это ровно та проблема, которую UUID версии 7 была призвана решить. Помещая миллисекундную метку времени в наиболее значимые биты, значения v7 упорядочены по времени: новые строки вставляются ближе к концу индекса, прямо как последовательное целое, сохраняя при этом децентрализованную, нескоординированную генерацию UUID. Вы получаете большую часть выгоды от локальности индекса автоинкрементного ключа без центральной последовательности. Для новых систем, желающих ключей UUID, v7 обычно — правильная версия именно по этой причине.
Практические заметки
- Храните UUID как нативный тип UUID или двоичный, а не как строку из 36 символов. Хранение текстовой формы тратит место и замедляет сравнения; двоичная форма — 16 байт.
- Используйте v7 для ключей, где важна производительность вставки, и v4, где вы намеренно не хотите никакой информации о времени, встроенной в id.
- Автоинкрементное целое всё ещё совершенно хорошо для одноузловой таблицы, которой никогда не нужны внешне сгенерированные id; UUID оправдывают себя, когда важны распределённость, непредсказуемость или предварительная генерация.
Инструмент UUID генерирует и v4, и v7 и декодирует встроенную метку времени значения v7, так что вы видите свойство упорядоченности напрямую, всё в вашем браузере.