Оригинал: Stop Writing Bad SQL: Try These Clever Tricks!
Перевод для канала Мы ж программист
Если вы когда-нибудь писали SQL-запрос и чувствовали, что просто угадываете, как его делается, вы не одиноки. Неважно, начинающий ли вы разработчик или опытный мастер бэкенда, скорее всего, в какой-то момент вам приходилось писать плохой SQL.
Но вот в чем дело: плохой SQL – это не просто неопрятный код. Он может замедлить работу всего приложения, запутать ваших коллег и даже открыть двери для уязвимостей в системе безопасности. 😱
Поэтому в этой статье мы расскажем о некоторых хитроумных трюках с SQL, которые помогут превратить ваши запросы из «не очень» в шедевр. Мы рассмотрим производительность, читаемость, удобство сопровождения и даже подсветим несколько проблем, которые большинство разработчиков упускают из виду.
Так что берите свои ☕ и давайте поднимем уровень вашей игры в SQL!
Как выглядит «плохой SQL»?
Прежде чем мы погрузимся в исправления, давайте определим, как на самом деле выглядит плохой SQL. Вот несколько примеров:
- Неоптимизированные SELECT, которые получают больше данных, чем нужно
- Отсутствующие индексы, приводящие к полному сканированию таблицы
- Жестко закодированные значения, которые делают запросы негибкими
- Плохая логика JOIN, которая приводит к сбоям при работе с большими наборами данных
- Непоследовательное именование, вызывающее путаницу
- Отсутствие комментариев или форматирования
Звучит знакомо? Не волнуйтесь – мы собираемся все это исправить!
1. Прекратите использовать SELECT *
– будьте конкретны!
Начнем с кардинального греха SQL:
SELECT * FROM users;
Несмотря на кажущуюся безобидность, этот запрос может стать катастрофой производительности, особенно если ваша таблица имеет дюжину столбцов (или больше).
Почему это плохо:
- Извлекает ненужные данные
- Усложняет отслеживание того, что действительно используется.
- Ломает код при изменении схемы.
Лучший способ:
SELECT id, name, email FROM users;
Профессиональный совет: Конкретность также помогает вашим инструментам ORM работать лучше и сокращает время передачи данных.
2. Используйте псевдонимы для упрощения и уточнения
Псевдонимы облегчают чтение запросов и помогают при работе с несколькими соединениями.
Плохо:
SELECT orders.id, customers.name, orders.date
FROM orders
JOIN customers ON orders.customer_id = customers.id;
Лучше:
SELECT o.id, c.name, o.date
FROM orders o
JOIN customers c ON o.customer_id = c.id;
Коротко, красиво и читабельно!
3. Индексы: Ваше секретное оружие для повышения скорости
Индексы – это как оглавление для вашей базы данных. Без них SQL должен считывать каждую строку (полное сканирование таблицы).
Как использовать индексы с умом:
- Индексируйте часто запрашиваемые столбцы (
WHERE
,JOIN
,ORDER BY
). - Избегайте чрезмерного индексирования – каждый индекс увеличивает нагрузку на запись.
- Используйте составные индексы для поиска по нескольким столбцам
CREATE INDEX idx_users_email ON users(email);
Профессиональный совет: Используйте EXPLAIN
, чтобы понять, использует ли запрос индексы или нет.
4. Нормализуйте (но не переусердствуйте!)
Нормализация базы данных помогает устранить избыточность, но чрезмерная нормализация может привести к чрезмерному количеству соединений.
Лучшая практика:
- Используйте 3-ю нормальную форму (3NF) в качестве базового уровня.
- Отказывайтесь от нормализации только тогда, когда это оправдано производительностью
Плохо:
SELECT *
FROM orders
JOIN customers ON orders.customer_id = customers.id
JOIN addresses ON customers.address_id = addresses.id
JOIN regions ON addresses.region_id = regions.id;
Лучше:
Иногда хранение часто используемой информации (например, названия региона) в таблице заказов помогает избежать сложных объединений в запросах, выполняемых в режиме реального времени.
5. Агрегатные функции, выполненные правильно
Такие функции, как COUNT()
, SUM()
и AVG()
, являются мощными – но часто используются неправильно.
Избегайте этого:
SELECT COUNT(*) FROM orders WHERE status = 'shipped';
Хотя этот способ работает, он может быть неэффективным для больших таблиц.
Вместо этого рассмотрите следующие варианты:
- Индексирование столбца
status
- Создание материализованного представления (materialized view), если запрос частый
6. Используйте CTE для удобочитаемости (используем WITH)
Common Table Expressions (CTE) разбивают сложные запросы на понятные блоки. Думайте о них как о временных именованных подзапросах.
WITH recent_orders AS (
SELECT * FROM orders WHERE order_date >= CURRENT_DATE - INTERVAL '30 days'
)
SELECT customer_id, COUNT(*)
FROM recent_orders
GROUP BY customer_id;
Чище, проще для отладки и полностью модульно!
7. Очищайте вводные данные, всегда!
SQL-инъекции по-прежнему остаются одним из самых опасных эксплойтов.
Никогда не делайте так:
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
Лучше так:
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
Используйте подготовленные запросы (prepared statements) или ORM, которые очищают данные автоматически.
8. Анализируйте и оптимизируйте с помощью EXPLAIN
и ANALYZE
Это ваши рентгеновские очки, позволяющие понять, что на самом деле делает SQL под капотом.
EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';
Ищите:
- Последовательное сканирование
- Использование индексов
- Типы соединений (join)
Используйте инструменты pgAdmin или MySQL Workbench для визуального анализа.
9. Делайте запросы предсказуемыми (избегайте случайностей)
Используете ORDER BY RAND()
, чтобы перетасовать строки?
SELECT * FROM users ORDER BY RAND() LIMIT 1;
Это не масштабируется и может привести к падению сервера при работе с большими таблицами.
Альтернативы:
- Использовать случайное смещение (если таблица маленькая).
- Предварительно вычислять случайные выборки и хранить их
- Использовать TABLESAMPLE (в PostgreSQL)
10. Ограничьте то, что нужно
Не стоит получать 10 000 строк только для того, чтобы отобразить 10.
Плохо:
SELECT * FROM logs;
Лучше:
SELECT * FROM logs ORDER BY created_at DESC LIMIT 10;
Для получения предсказуемых результатов всегда используйте LIMIT
в паре с ORDER BY
.
11. Пакетные вставки и обновления больших объемов
Пытаетесь вставить 100 000 строк за один раз?
Скорее всего, это вызовет проблемы с памятью или таймаут.
Более разумная стратегия:
- Разбейте на партии (например, по 1000 строк).
- Используйте транзакции для обеспечения атомарности
- Используйте инструменты массовой вставки (
COPY
,LOAD DATA
и т. д.).
12. Не злоупотребляйте подзапросами
Подзапросы внутри WHERE
или SELECT
могут стать узкими местами в производительности.
Вместо:
SELECT name FROM customers
WHERE id IN (SELECT customer_id FROM orders WHERE total > 500);
Попробуйте:
SELECT DISTINCT c.name
FROM customers c
JOIN orders o ON c.id = o.customer_id
WHERE o.total > 500;
JOINы
в общем случае более оптимизированы, чем подзапросы.
13. Поддерживайте чистоту кода с помощью форматирования
Вы бы не стали писать на Python нечитаемый код – так не делайте этого с SQL!
Используйте такие инструменты, как:
Форматируйте запросы так:
SELECT
c.name,
COUNT(o.id) AS total_orders
FROM
customers c
JOIN
orders o ON c.id = o.customer_id
GROUP BY
c.name
ORDER BY
total_orders DESC;
Выглядит красиво, правда?
Дополнительные советы: Мониторинг и настройка со временем
SQL – это не «написал один раз и забыл». По мере роста ваших данных должны расти и ваши усилия по оптимизации.
- Используйте журналы запросов для поиска медленных запросов
- Следите за показателями производительности
- Периодически проводите вакуум/анализ (PostgreSQL).
- При необходимости архивируйте старые данные.
SQL дзен – это постоянная, внимательная настройка.
Заключительные мысли
Написать хороший SQL – это не значит заучить тысячу функций, это значит ясно мыслить, понимать свои данные и делать разумный выбор.
Вооружившись этими умными приемами, вы сможете избежать типичных ошибок, которые подстерегают многие проекты, и вместо этого создать чистый, эффективный и масштабируемый SQL.
В следующий раз, когда вы будете писать запрос, помните: вы не просто общаетесь с базой данных – вы определяете будущую производительность всего вашего приложения.
А теперь отправляйтесь на рефакторинг того уродливого запроса, которого вы так долго избегали!