Разберите SQL‑кейc по производительности: SELECT * FROM orders WHERE status = 'open' OR processed = 0; — почему запрос может работать медленно на больших таблицах, как индексирование, переписывание через UNION/UNION ALL или использование составных индексов изменят план выполнения и производительность?

11 Янв в 06:34
40 +1
0
Ответы
1
Коротко — почему медленно
- Оператор OR по двум разным столбцам часто мешает оптимизатору использовать один эффективный индекс и приводит к полному просмотру таблицы (full table scan) или к объединению результатов нескольких индексов (index merge / bitmap OR). При большом объёме таблицы это даёт много I/O и случайных обращений, особенно если возвращается большая доля строк.
- SELECT * снижает шанс индекс‑only (covering) скана: если индекс не покрывает все столбцы, после поиска по индексу нужны дополнительные чтения строк.
Как думает планировщик (общая модель)
- Полный скан: стоимость ≈ чтение всех строк ∝ NNN.
- Два индексных поиска + объединение: возвращаемые строки n1n_1n1 (для status='open') и n2n_2n2 (для processed=000), пересечение n12n_{12}n12 . Общее число уникальных строк n=n1+n2−n12n = n_1 + n_2 - n_{12}n=n1 +n2 n12 . Стоимость ≈ суммарная стоимость индексов + чтения nnn строк (много случайных I/O, если строки разбросаны).
- Оптимизатор выберет индексный путь, если ожидаемая стоимость < full scan; при большой доле совпадающих строк он обычно выберет full scan.
Реакция разных СУБД
- PostgreSQL: делает две индексные выборки и BitmapOr → bitmap heap scan; эффективно при достаточной селективности индексов.
- MySQL (InnoDB): может применять index_merge (union/intersect) или отказаться и взять full scan; index_merge иногда медленнее.
- Oracle/SQL Server: используют bitmap/union план, аналогично — эффективность зависит от селективности и покрытия индекса.
Переписывание через UNION / UNION ALL
- Прямой вариант:
SELECT * FROM orders WHERE status = 'open'
UNION
SELECT * FROM orders WHERE processed = 000;
- UNION удалит дубликаты (сорт/агрегация) — дополнительная стоимость.
- Быстрее и без дубликатов (если status не NULL):
SELECT * FROM orders WHERE status = 'open'
UNION ALL
SELECT * FROM orders WHERE processed = 000 AND status 'open';
- Здесь каждая часть может использовать свой одноколоночный индекс (index seek), результат — конкатенация без лишнего сортирования. В сумме это обычно быстрее, чем OR, если индексы селективны и возвращают небольшую часть таблицы.
- Минус: если много строк подходят под обе ветви, всё равно нужно читать/выводить много данных.
Составные и покрывающие индексы
- Составной индекс (status, processed) помогает, когда есть фильтр по префиксному столбцу (status). Но при OR между status и processed составной индекс обычно не решает задачу, потому что второе условие по processed нельзя искать по индексу, если первый столбец не ограничен.
- Составной индекс (processed, status) позволит быстро обслужить ветвь processed=000 и при правильном покрытии (если индекс включает все требуемые столбцы) сделает индекс‑only скан. Но ветвь status='open' может всё равно требовать отдельного индекса.
- Лучший вариант для OR на разных столбцах — иметь отдельные индексы на каждом столбце или переписать запрос так, чтобы каждая ветвь использовала свой индекс, и по возможности делать покрывающие индексы (INCLUDE / включённые столбцы) чтобы избежать lookup по таблице.
Практические рекомендации
1. Запустите EXPLAIN / EXPLAIN ANALYZE — смотрите выбранный план и оценочные/реальные строки.
2. Если ожидаемая доля совпадающих строк мала (малый n/Nn/Nn/N), создайте отдельные индексы на status и processed и перепишите через UNION ALL с условием, исключающим дубли.
3. Если одна из ветвей очень селективна, рассмотрите составной индекс с этой колонкой первой, или покрывающий индекс, чтобы получить index‑only скан.
4. Если большая часть таблицы удовлетворяет условиям (например n/Nn/Nn/N велико, скажем > ~10–20% — конкретный порог зависит от СУБД), индексное решение может быть хуже полного скана; тогда оставьте full scan или пересмотрите модель (партиционирование, хранение статуса отдельно).
5. Сократите SELECT * до нужных столбцов или используйте INCLUDE/covering indexes (SQL Server, PostgreSQL expression indexes / INCLUDE в PG 11+ через INCLUDE) чтобы уменьшить random I/O.
6. Обновляйте статистику, тюньте параметры (bitmap threshold в PostgreSQL/MySQL), профилируйте.
Краткий вывод
- OR по разным столбцам часто ведёт к неэффективному плану (full scan или дорогой index merge).
- Переписывание в UNION ALL + отдельные индексы обычно даёт более предсказуемый и быстрый план, если ветви селективны и вы исключаете дубликаты.
- Составной индекс полезен только если порядок колонок соответствует фильтру; для OR на разных столбцах чаще нужны отдельные индексы или покрывающие индексы.
11 Янв в 06:44
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир