Какие условия нельзя делать в where?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Условия, которые нельзя делать в WHERE (для оптимизации)
Есть определённые типы условий в WHERE, которые предотвращают использование индексов и приводят к полному сканированию таблицы (Table Scan). Это критично для производительности больших таблиц.
1. Функции над столбцами (Function Calls)
Это главный враг индексов. Если применяешь функцию к столбцу, индекс больше не работает.
-- ❌ ПЛОХО: функция над столбцом (Full Table Scan)
SELECT * FROM users WHERE UPPER(name) = "JOHN";
SELECT * FROM users WHERE EXTRACT(YEAR FROM created_at) = 2024;
SELECT * FROM users WHERE LENGTH(email) > 10;
SELECT * FROM users WHERE SUBSTRING(phone, 1, 3) = "555";
SELECT * FROM products WHERE price * 1.1 > 100; -- Вычисление в WHERE
-- ✅ ХОРОШО: функция на константе или переписываем условие
SELECT * FROM users WHERE name = "john"; -- Как есть, без функции
SELECT * FROM users
WHERE created_at >= "2024-01-01" AND created_at < "2025-01-01";
SELECT * FROM users WHERE email LIKE "%@%._%" AND LENGTH(email) > 10;
SELECT * FROM users WHERE phone LIKE "555%";
SELECT * FROM products WHERE price > 90.9; -- 100 / 1.1
Почему это плохо: БД должна выполнить функцию для каждой строки, прежде чем проверить условие. Индекс хранит значения столбцов, но не результаты функций.
2. Операторы LIKE с wildcards в начале
Поиск по шаблону с % в начале не использует индекс.
-- ❌ ПЛОХО: % в начале (Full Table Scan)
SELECT * FROM users WHERE email LIKE "%gmail.com";
SELECT * FROM users WHERE name LIKE "%john%";
SELECT * FROM products WHERE description LIKE "%red%";
-- ✅ ХОРОШО: % в конце (использует индекс)
SELECT * FROM users WHERE email LIKE "john%";
SELECT * FROM users WHERE email LIKE "john@%";
-- ✅ ЛУЧШЕ: точный поиск или другой подход
SELECT * FROM users WHERE email LIKE "john@gmail.com";
-- Или для поиска по части, лучше использовать FTS (Full Text Search)
SELECT * FROM users WHERE to_tsvector(description) @@ to_tsquery("red");
Исключение: Некоторые БД (как PostgreSQL) поддерживают специальные индексы для поиска по префиксу.
3. Использование OR с разными столбцами
OR часто приводит к отказу от индекса.
-- ❌ ПЛОХО: OR с несколькими столбцами
SELECT * FROM users WHERE id = 1 OR email = "john@example.com";
SELECT * FROM orders WHERE user_id = 5 OR status = "completed";
-- ✅ ХОРОШО: используй UNION (часто быстрее)
SELECT * FROM users WHERE id = 1
UNION
SELECT * FROM users WHERE email = "john@example.com";
-- ✅ ХОРОШО: если оба столбца в составном индексе
CREATE INDEX idx_users ON users(id, email);
SELECT * FROM users WHERE id = 1 OR email = "john@example.com";
-- ❌ ПЛОХО: OR с разными столбцами разных таблиц
SELECT * FROM orders WHERE user_id = 1 OR product_id = 5;
-- Нужны индексы на обоих столбцах, но БД может выбрать только один
Примечание: Современные оптимизаторы улучшились, но не надейся на это.
4. Неравенства со сложными выражениями
Вычисления в WHERE часто приводят к Full Table Scan.
-- ❌ ПЛОХО: вычисления
SELECT * FROM products WHERE price * quantity > 1000;
SELECT * FROM users WHERE TIMESTAMPDIFF(YEAR, created_at, NOW()) > 5;
SELECT * FROM orders WHERE amount - discount > 100;
-- ✅ ХОРОШО: переносим вычисления на другую сторону
SELECT * FROM products WHERE price > 1000 / quantity;
SELECT * FROM users WHERE created_at < NOW() - INTERVAL 5 YEAR;
SELECT * FROM orders WHERE amount > 100 + discount;
-- ✅ ЛУЧШЕ: использми индекс для результата (вычисленный столбец)
ALTER TABLE products ADD COLUMN total_value GENERATED AS (price * quantity);
CREATE INDEX idx_total_value ON products(total_value);
SELECT * FROM products WHERE total_value > 1000;
5. NOT и отрицания
Отрицание часто предотвращает использование индекса.
-- ❌ ПЛОХО: NOT часто приводит к Full Table Scan
SELECT * FROM users WHERE NOT status = "active";
SELECT * FROM orders WHERE NOT user_id = 5;
SELECT * FROM products WHERE NOT category IN ("electronics", "books");
-- ✅ ХОРОШО: переписываем позитивно
SELECT * FROM users WHERE status != "active"; # Или status <> "active"
SELECT * FROM users WHERE status IN ("inactive", "deleted");
SELECT * FROM orders WHERE user_id != 5;
SELECT * FROM products WHERE category NOT IN ("electronics", "books");
-- ✅ ЛУЧШЕ: используй IS NULL вместо NOT EXISTS
SELECT * FROM users WHERE deleted_at IS NULL; -- Лучше
SELECT * FROM users WHERE NOT deleted_at IS NOT NULL; -- Хуже
6. Регулярные выражения (REGEXP, LIKE с паттернами)
Регулярные выражения требуют полного сканирования строк.
-- ❌ ПЛОХО: regex (Full Table Scan)
SELECT * FROM users WHERE email REGEXP "^[a-z]+@gmail\\.com$";
SELECT * FROM products WHERE sku REGEXP "^PROD-[0-9]{4}$";
-- ✅ ХОРОШО: точный поиск
SELECT * FROM users WHERE email LIKE "%@gmail.com";
SELECT * FROM products WHERE sku LIKE "PROD-%";
-- ✅ ЛУЧШЕ: используй индексы правильно
SELECT * FROM users WHERE email LIKE "%.%@gmail.com";
7. NULL сравнения (иногда)
В некоторых БД NULL сравнения не используют индекс.
-- ❌ ПЛОХО: в некоторых БД индекс не используется
SELECT * FROM users WHERE phone = NULL;
-- ✅ ПРАВИЛЬНО: всегда используй IS NULL
SELECT * FROM users WHERE phone IS NULL;
SELECT * FROM users WHERE phone IS NOT NULL;
-- Совет: если часто проверяешь на NULL, создай индекс
CREATE INDEX idx_phone_null ON users(phone) WHERE phone IS NULL;
8. Приведение типов (Type Conversion)
Неявное приведение типов может сломать индекс.
-- ❌ ПЛОХО: сравнение разных типов
SELECT * FROM users WHERE id = "123"; -- id int, сравниваем со string
SELECT * FROM products WHERE price = "99.99"; -- price decimal vs string
-- ✅ ПРАВИЛЬНО: используй правильные типы
SELECT * FROM users WHERE id = 123;
SELECT * FROM products WHERE price = 99.99;
-- ⚠️ Особенно опасно в Python/ORM
# ❌ ПЛОХО
user = User.objects.filter(id="123") # строка вместо int
# ✅ ХОРОШО
user = User.objects.filter(id=123) # правильный тип
9. Использование IN с подзапросом (иногда)
Подзапросы в IN могут быть медленными на больших наборах.
-- ❌ ПЛОХО: подзапрос в IN (может быть медленнее)
SELECT * FROM orders WHERE user_id IN (
SELECT id FROM users WHERE created_at > "2024-01-01"
);
-- ✅ ХОРОШО: используй JOIN
SELECT DISTINCT o.* FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.created_at > "2024-01-01";
-- ✅ ХОРОШО: если IN список маленький (< 10 элементов)
SELECT * FROM orders WHERE user_id IN (1, 2, 3, 4, 5);
10. Плохой порядок условий
Порядок условий может влиять на производительность (в некоторых случаях).
-- ❌ МОЖЕТ быть медленнее: селективное условие в конце
SELECT * FROM orders
WHERE status = "completed" AND amount > 1000;
-- Если "completed" редкий статус, но у amount > 1000 миллионы строк
-- ✅ ЛУЧШЕ: селективное условие первым
SELECT * FROM orders
WHERE amount > 1000 AND status = "completed";
-- Если amount > 1000 встречается реже
-- ⚠️ На самом деле, оптимизатор сам переупорядочит,
-- но зависит от статистики и версии БД
Таблица сравнения
| Условие | Использует индекс | Проблема | Решение |
|---|---|---|---|
| column = value | ✅ Да | Нет | - |
| UPPER(column) = value | ❌ Нет | Функция | Убрать функцию |
| column LIKE "%abc%" | ❌ Нет | % в начале | Использовать LIKE "abc%" |
| column LIKE "abc%" | ✅ Да | Нет | - |
| column IN (SELECT ...) | ⚠️ Иногда | Подзапрос | Использовать JOIN |
| column NOT IN (...) | ❌ Нет | NOT | Переписать условие |
| column != value | ⚠️ Иногда | Неравенство | Зависит от БД |
| column IS NULL | ✅ Да* | Специальный индекс | Создать индекс |
| column OR ... | ❌ Нет | OR | Использовать UNION |
| CAST(column AS type) | ❌ Нет | Приведение типов | Использовать правильный тип |
Python ORM примеры
# ❌ ПЛОХО
from sqlalchemy import func
# Функция в WHERE
users = session.query(User).filter(func.upper(User.name) == "JOHN")
# OR множество столбцов
orders = session.query(Order).filter(
(Order.user_id == 1) | (Order.status == "completed")
)
# ✅ ХОРОШО
# Используй как есть
users = session.query(User).filter(User.name == "John")
# Используй and/or правильно
from sqlalchemy import and_, or_
# Лучше разделить
orders1 = session.query(Order).filter(Order.user_id == 1)
orders2 = session.query(Order).filter(Order.status == "completed")
result = orders1.union(orders2)
# Или используй JOIN
result = session.query(Order).join(User).filter(
(Order.user_id == User.id) & (User.created_at > "2024-01-01")
)
Профилирование запросов
-- PostgreSQL: EXPLAIN ANALYZE
EXPLAIN ANALYZE
SELECT * FROM orders WHERE UPPER(user_name) = "JOHN";
-- MySQL: EXPLAIN
EXPLAIN
SELECT * FROM orders WHERE UPPER(user_name) = "JOHN";
-- Смотрим на:
-- 1. type: ALL (Full Scan), INDEX, RANGE, REF (индекс используется)
-- 2. rows: сколько строк скканировано
-- 3. Extra: "Using index" (хорошо), "Using where" (может быть медленно)
Best Practices
- Никогда не применяй функции к столбцам в WHERE
- Используй точный поиск вместо LIKE с %
- Избегай OR, используй UNION или IN
- Старайся переписать условия позитивно (без NOT)
- Всегда используй правильные типы данных
- Профилируй запросы с EXPLAIN
- Создавай индексы на часто используемых столбцах в WHERE
- Помни, что на большие таблицы (миллионы строк) это критично
Эти правила критичны для production систем с большими объёмами данных.