♾️ Дырявый UNIQUE: как индекс пропускает дубликаты
Ты создал ограничение UNIQUE и уверен, что база под надёжной защитой. Но она радостно пропустит в таблицу хоть миллион строк с пустыми значениями.
⚙️ Почему так происходит
🔹 По стандарту SQL значение
🔹 Равна ли одна неизвестность другой? Нет. Правило гласит:
🔹 Раз пустые значения не равны друг другу, ограничение уникальности их пропускает. PostgreSQL, MySQL и Oracle позволят создавать бесконечные пустые профили.
⚠️ Сюрприз от SQL Server
В MS SQL Server логика другая. Он считает
🚀 Что делать
🔸 Если поле всегда должно быть заполнено — добавляй ограничение
🔸 В PostgreSQL 15+ можно явно запретить дубликаты пустых строк правилом
💡 Уникальность защищает только известные данные, а для пустых нужны свои правила.
Ты создал ограничение UNIQUE и уверен, что база под надёжной защитой. Но она радостно пропустит в таблицу хоть миллион строк с пустыми значениями.
⚙️ Почему так происходит
🔹 По стандарту SQL значение
NULL означает «неизвестность».🔹 Равна ли одна неизвестность другой? Нет. Правило гласит:
NULL != NULL.🔹 Раз пустые значения не равны друг другу, ограничение уникальности их пропускает. PostgreSQL, MySQL и Oracle позволят создавать бесконечные пустые профили.
CREATE TABLE users (
email VARCHAR(100) UNIQUE
);
-- База сохранит обе строки:
INSERT INTO users (email) VALUES (NULL), (NULL);
⚠️ Сюрприз от SQL Server
В MS SQL Server логика другая. Он считает
NULL конкретным значением и разрешает добавить его только один раз. Вторая пустая строка выдаст ошибку.🚀 Что делать
🔸 Если поле всегда должно быть заполнено — добавляй ограничение
NOT NULL.🔸 В PostgreSQL 15+ можно явно запретить дубликаты пустых строк правилом
UNIQUE NULLS NOT DISTINCT.💡 Уникальность защищает только известные данные, а для пустых нужны свои правила.
❤8👍7🤯2
⏳ Конец хаосу: почему UUIDv7 — хороший выбор для Primary Key
Обычный автоинкремент (ID=1, 2, 3), если показывать его наружу, позволяет конкурентам легко высчитать ваши продажи. А популярный случайный UUIDv4 незаметно снижает производительность базы.
⚠️ Две крайности старых подходов
🔷 Автоинкремент: быстрый и компактный, но раскрывает объёмы. Если зарегистрироваться дважды с разницей в день, конкурент узнает точное число ваших новых клиентов.
🔷 UUIDv4: абсолютно случаен. Каждая новая запись попадает в случайное место индекса. Это как вписывать новые контакты на случайные страницы бумажной телефонной книги: страницы приходится постоянно делить, индекс раздувается, а
🚀 Магия UUIDv7
Свежий стандарт (RFC 9562, 2024) решает обе проблемы. В начало ключа записывается текущее время (Timestamp) с точностью до миллисекунды, а остальная часть остаётся случайной.
В версиях до 18 нужно расширение
📈 Что мы получаем
🔶 Новые записи ложатся в B-дерево почти последовательно, в конец индекса.
🔶 Индекс меньше раздувается, и вставка заметно быстрее, чем с UUIDv4.
🔶 Идентификаторы уникальны, и посчитать по ним объёмы нельзя. Но время создания записи из них читается.
💡 UUIDv7 — хороший компромисс: вставка почти как у счётчика, и объёмы не утекают. Но он вдвое больше
Обычный автоинкремент (ID=1, 2, 3), если показывать его наружу, позволяет конкурентам легко высчитать ваши продажи. А популярный случайный UUIDv4 незаметно снижает производительность базы.
⚠️ Две крайности старых подходов
🔷 Автоинкремент: быстрый и компактный, но раскрывает объёмы. Если зарегистрироваться дважды с разницей в день, конкурент узнает точное число ваших новых клиентов.
🔷 UUIDv4: абсолютно случаен. Каждая новая запись попадает в случайное место индекса. Это как вписывать новые контакты на случайные страницы бумажной телефонной книги: страницы приходится постоянно делить, индекс раздувается, а
INSERT на больших таблицах замедляется.🚀 Магия UUIDv7
Свежий стандарт (RFC 9562, 2024) решает обе проблемы. В начало ключа записывается текущее время (Timestamp) с точностью до миллисекунды, а остальная часть остаётся случайной.
-- PostgreSQL 18+
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuidv7(),
amount NUMERIC
);
В версиях до 18 нужно расширение
pg_uuidv7 с функцией uuid_generate_v7().📈 Что мы получаем
🔶 Новые записи ложатся в B-дерево почти последовательно, в конец индекса.
🔶 Индекс меньше раздувается, и вставка заметно быстрее, чем с UUIDv4.
🔶 Идентификаторы уникальны, и посчитать по ним объёмы нельзя. Но время создания записи из них читается.
💡 UUIDv7 — хороший компромисс: вставка почти как у счётчика, и объёмы не утекают. Но он вдвое больше
bigint и не секрет: доступ к данным всё равно проверяйте на сервере.🔥10❤7👍3🏆1