[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"question-sql-kak-optimizirovat-sql-zaprosy":3},{"id":4,"slug":5,"topicId":6,"topicSlug":7,"topicName":8,"topicEmoji":9,"question":10,"answer":11,"codeLang":12,"codeSrc":12,"important":12,"commonMistakes":12,"modernUsage":12,"difficulty":13,"tags":14,"related":15,"progress":16,"seo":17},878,"kak-optimizirovat-sql-zaprosy",25,"sql","SQL","🗃️","Как оптимизировать SQL-запросы?","Оптимизация SQL-запросов — процесс улучшения производительности запросов, основанный на измерениях (`EXPLAIN ANALYZE`), а не на догадках. Ниже приведён практический чеклист.\n\n### Сводный чеклист\n\n| Приём | Когда применять |\n|---|---|\n| Создать индекс | Медленный `WHERE`, `JOIN`, `ORDER BY` |\n| Убрать `SELECT *` | Всегда |\n| Устранить N+1 | Циклические запросы в коде |\n| Keyset pagination | Глубокая пагинация (`OFFSET` > 1000) |\n| `EXISTS` вместо `IN` | Подзапрос возвращает много строк |\n| Избегать функций в `WHERE` | Функция на индексированном столбце |\n| Пул соединений | Всегда |\n| `EXPLAIN ANALYZE` | Любой медленный запрос |\n| Денормализация | Чтение >> запись |\n| Materialized View | Тяжёлые агрегатные запросы |\n\n### 1. Используйте индексы с умом\n\n```sql\n-- Плохо: функция на индексированном столбце — индекс не используется\nSELECT * FROM users WHERE UPPER(email) = 'USER@EXAMPLE.COM';\n\n-- Хорошо: функциональный индекс\nCREATE INDEX idx_users_email_upper ON users(UPPER(email));\n\n-- Ещё лучше: хранить данные в нужном формате\nSELECT * FROM users WHERE email = 'user@example.com';\n```\n\n### 2. Избегайте SELECT *\n\n```sql\n-- Плохо: читаются все столбцы, даже ненужные\nSELECT * FROM orders WHERE status = 'pending';\n\n-- Хорошо: только нужные столбцы (может использовать Index Only Scan)\nSELECT id, amount, created_at FROM orders WHERE status = 'pending';\n```\n\n### 3. Устраните проблему N+1\n\n```java\n\u002F\u002F Плохо (N+1): один запрос на список + N запросов на детали\nList\u003CUser> users = query(\"SELECT * FROM users\");\nfor (User u : users) {\n    List\u003COrder> orders = query(\"SELECT * FROM orders WHERE user_id = ?\", u.id);\n}\n\n\u002F\u002F Хорошо: один запрос с JOIN\nquery(\"SELECT u.*, o.* FROM users u LEFT JOIN orders o ON o.user_id = u.id\");\n```\n\n### 4. Используйте keyset pagination вместо OFFSET\n\n```sql\n-- Медленно на глубоких страницах (СУБД читает и отбрасывает 10000 строк)\nSELECT * FROM products ORDER BY id LIMIT 20 OFFSET 10000;\n\n-- Быстро: keyset pagination (курсорная пагинация)\nSELECT * FROM products WHERE id > 10020 ORDER BY id LIMIT 20;\n```\n\n### 5. Предпочитайте EXISTS вместо IN для подзапросов\n\n```sql\n-- EXISTS останавливается при первом совпадении\nSELECT * FROM users u\nWHERE EXISTS (\n    SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.amount > 1000\n);\n```\n\n### 6. Не применяйте функции к индексированным столбцам\n\n```sql\n-- Плохо: индекс на created_at не используется\nSELECT * FROM orders WHERE EXTRACT(YEAR FROM created_at) = 2026;\n\n-- Хорошо: диапазонный запрос, индекс используется\nSELECT * FROM orders\nWHERE created_at >= '2026-01-01' AND created_at \u003C '2027-01-01';\n```\n\n### 7. Используйте пул соединений\n\nСоздание TCP-соединения к PostgreSQL — дорогая операция (~100-200ms). Используйте пул: PgBouncer (внешний пулер), HikariCP (в Java-приложениях), или встроенный пул Spring Boot.\n\n### 8. Материализованные представления\n\n\u003Cdetails>\u003Csummary>Пример с Materialized View\u003C\u002Fsummary>\n\n```sql\nCREATE MATERIALIZED VIEW mv_daily_stats AS\nSELECT\n    DATE(created_at) AS day,\n    COUNT(*) AS order_count,\n    SUM(amount) AS total_amount\nFROM orders\nGROUP BY DATE(created_at);\n\nCREATE INDEX idx_mv_daily_stats_day ON mv_daily_stats(day);\n\n-- Использование (мгновенный ответ)\nSELECT * FROM mv_daily_stats WHERE day >= '2026-01-01';\n\n-- Обновление данных (CONCURRENTLY — без блокировки чтения)\nREFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_stats;\n```\n\n\u003C\u002Fdetails>\n\n### 9. Денормализация как компромисс\n\nДенормализация ускоряет чтение за счёт хранения избыточных данных, но усложняет обновление и может привести к аномалиям. Используйте, когда чтение значительно преобладает над записью.\n\n### Важное\n\n- Оптимизация должна быть основана на измерениях, а не на догадках\n- Не оптимизируйте преждевременно — сначала убедитесь, что запрос является узким местом\n- Регулярно запускайте `ANALYZE` для актуальной статистики\n- `pg_stat_statements` — расширение для отслеживания самых медленных и частых запросов\n- При высокой нагрузке рассмотрите read replicas для разделения чтения и записи\n\n### Частые ошибки\n\n- Создают индексы «на всякий случай» без проверки их использования\n- Используют `OFFSET` для глубокой пагинации — крайне медленно\n- Кешируют в приложении то, что лучше кешировать на уровне СУБД (materialized views)\n- Оптимизируют запрос на тестовых данных (100 строк), а на production (10 млн строк) план совершенно другой\n\n> **На собеседовании:** не перечисляйте все приёмы — опишите системный подход: измерить (`EXPLAIN ANALYZE`) -> найти узкое место -> применить конкретный приём. Частая ошибка — начать с «добавить индекс» без понимания, где именно проблема.","","senior",[7],[],null,{"title":18,"description":19,"ogTitle":18,"ogDescription":20,"keywords":21,"schemaAnswer":22,"featuredSnippetReady":23},"Как оптимизировать SQL-запросы? — Gymterview","Оптимизация SQL-запросов — процесс улучшения производительности запросов, основанный на измерениях (`EXPLAIN ANALYZE`), а не на догадках. Ниже приведён практиче","Оптимизация SQL-запросов — процесс улучшения производительности запросов, основанный на измерениях (`EXPLAIN ANALYZE`), ",[7,13],"Оптимизация SQL-запросов — процесс улучшения производительности запросов, основанный на измерениях (`EXPLAIN ANALYZE`), а не на догадках. Ниже приведён практический чеклист.",true]