Добавить в корзинуПозвонить
Найти в Дзене
Цифровая Переплавка

⚡️ Почему OR в SQL — это скрытый убийца производительности

На первый взгляд, оператор OR в SQL кажется безобидным. Нужен фильтр по нескольким условиям? Просто напиши: select count(*)
from application
where submitter_id = :user_id
or reviewer_id = :user_id; Но в реальности такой запрос может оказаться в 100 раз медленнее, чем переписанный на UNION или два подзапроса. И это не баг, а фундаментальная особенность работы планировщиков запросов. Дело в том, что:
⚡️ AND уменьшает количество данных, сузив выборку.
💥 OR расширяет набор данных, заставляя движок либо делать дорогое объединение, либо идти в полный скан таблицы. Даже при наличии индексов, оптимизатор вынужден: В примере из статьи: запрос с OR занимал 100 мс, а переписанный на подзапросы — меньше 1 мс. 💡 Использовать UNION вместо OR select count(*) from application where submitter_id = :user_id
union all
select count(*) from application where reviewer_id = :user_id; 💡 Перепроектировать схему
Вместо хранения двух колонок (submitter_id, reviewer_id) можно вынести их в отдельную таблицу app
Оглавление

На первый взгляд, оператор OR в SQL кажется безобидным. Нужен фильтр по нескольким условиям? Просто напиши:

select count(*)
from application
where submitter_id = :user_id
or reviewer_id = :user_id;

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

🔍 Почему так происходит

Дело в том, что:
⚡️
AND уменьшает количество данных, сузив выборку.
💥
OR расширяет набор данных, заставляя движок либо делать дорогое объединение, либо идти в полный скан таблицы.

Даже при наличии индексов, оптимизатор вынужден:

  • либо выполнить два поиска и объединить результаты (Bitwise OR),
  • либо сделать sequential scan, прогнав по памяти миллионы строк.

В примере из статьи: запрос с OR занимал 100 мс, а переписанный на подзапросы — меньше 1 мс.

🛠 Технические приёмы оптимизации

💡 Использовать UNION вместо OR

select count(*) from application where submitter_id = :user_id
union all
select count(*) from application where reviewer_id = :user_id;

💡 Перепроектировать схему
Вместо хранения двух колонок (submitter_id, reviewer_id) можно вынести их в отдельную таблицу application_user с типом связи:

create table application_user (
user_id int8 not null,
application_id int8 not null,
role enum('submitter', 'reviewer') not null
);

Тогда поиск становится линейным: :user_id -> application_user -> application.

💡 Compound indexes и extended statistics
Если OR всё-таки нужен, стоит заранее подумать о
составных индексах или расширенной статистике в PostgreSQL (CREATE STATISTICS), чтобы оптимизатор принимал правильные решения.

🔮 Моё видение

Я часто замечал, что разработчики относятся к SQL как к «чёрному ящику»: написал запрос → получил ответ. Но на продакшене такие «безобидные OR» превращаются в узкие места, которые сжигают процессор и дисковый кеш.

Важно помнить:
🧩 SQL — это декларативный язык. Мы описываем «что», но не «как». Планировщик решает сам, и именно здесь кроются неожиданности.
⚙️ Хороший DBA всегда держит в голове распределение данных: популярные значения, селективность, кардинальность.
📊 Производительность — это не только про индексы, но и про дизайн схемы. Иногда проще
поменять модель данных, чем изощряться с запросами.

🧠 Личный инсайт

На мой взгляд, тезис «OR дорогой» — это не просто частный случай. Это напоминание о том, что дизайн базы данных и запросов всегда должен исходить из реальных паттернов доступа. Если система не рассчитана на «OR-запросы», то и проектировать её нужно так, чтобы они не понадобились.

Именно поэтому опытные архитекторы БД часто начинают с вопросов:

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

Только потом они проектируют таблицы и индексы.

🔗 Источники: