Оптимизация производительности баз
5 subscribers
Download Telegram
Транзакции и уровни изоляции: где ломается логика и начинается блокировка

Коллеги, давайте разберем план выполнения. Транзакция — это не магия «всё или ничего», а договор с СУБД: читаем, пишем и блокируем только в рамках выбранной изоляции.

— Read Committed спасает от грязных чтений, но не от неповторяемых: два одинаковых SELECT могут увидеть разное.
— Repeatable Read держит стабильные строки, но фантомы и конкуренция за диапазоны часто вылезают боком.
— Serializable дает самую жесткую семантику, но за нее платят ожиданием, дедлоками и падением пропускной способности.

Посмотрим, что тут с I/O в реальности: длинная транзакция — это не «аккуратнее», а больше удержанных версий, дольше живущие блокировки и грязный хвост для обслуживания. Если внутри есть сетевые вызовы, внешние API или тяжелая обработка, вы растягиваете блокировку там, где она вообще не нужна. В продакшене так лучше не делать, и вот почему: база не знает, что вы «почти закончили».

Золотое правило: сначала мониторинг, потом индексы. Смотрите на wait events, блокировки, дедлоки, время жизни транзакций и реальные точки конкуренции. Часто достаточно сократить область транзакции, вынести чтение наружу или разделить «проверить» и «изменить» на два коротких шага.

Схема простая, но дьявол кроется в статистике: выбирайте минимально достаточную изоляцию и держите транзакции короткими. Если нужен строгий порядок — фиксируйте его явно, а не надеждой на удачу.
Сложный SQL не тормозит сам по себе — его ломают планы, селективность и лишний I/O

Коллеги, давайте разберем план выполнения. У тяжёлого запроса обычно три источника боли: сканы там, где нужен seek; плохая оценка кардинальности; лишние сортировки и хэши на больших объёмах. Схема простая, но дьявол кроется в статистике.

Что проверять первым:
— совпадает ли порядок условий с индексом;
— нет ли функций по колонке в WHERE/JOIN;
— не тащите ли лишние поля в SELECT, если они не нужны;
— не превращает ли OR нормальный план в полный перебор.

Посмотрим, что тут с I/O в реальности: если план красивый, но читает слишком много страниц, оптимизация начинается не с «добавим индекс», а с сокращения входного набора. Фильтры выносят как можно раньше, тяжёлые JOIN'ы — после отсечения мусора, агрегации — только после уменьшения строк.

Ещё один частый провал — CTE и подзапросы, которые читаются как «логика», а исполняются как отдельные тяжёлые этапы. Иногда помогает разнести запрос на шаги с временной таблицей: да, это лишняя запись на диск, но она дешевле бесконечных пересчётов. В продакшене так лучше не делать, и вот почему: без замера можно просто перенести боль из CPU в I/O. ⚠️

Золотое правило: сначала мониторинг, потом индексы. Снимите план, фактическое число строк, чтения, блокировки; только потом меняйте текст запроса. Иначе вы лечите не причину, а форму симптома.


Если понравилось — посмотри @team_productivity_stack_arb
Сложный SQL тормозит не из-за “плохого сервера”, а из-за неудачного плана

Коллеги, давайте разберем план выполнения. У сложных запросов почти всегда ломается одно из трёх: фильтрация слишком поздняя, JOIN раздувает промежуточный набор, сортировка или группировка съедает память и уходит в temp. Сначала смотрим не на текст SQL, а на фактический план: где больше всего строк, где есть Seq Scan, nested loop на больших объёмах и лишние пересчёты.

Дальше работаем по порядку:
— проталкиваем фильтры как можно раньше;
— убираем функции с колонок в WHERE и JOIN, если нужен индекс;
— проверяем, не тащит ли CTE/подзапрос лишние строки;
— заменяем SELECT * на нужные поля, особенно перед JOIN и сортировкой;
— пересматриваем порядок соединений, если одна таблица маленькая, а другая раздута.
Золотое правило: сначала мониторинг, потом индексы.

Если запрос агрегирует данные, ищите место, где можно сократить набор до JOIN. Иногда выгоднее сначала отобрать ключи, потом присоединять детали. Если есть OR по разным полям, план часто деградирует в обход индексов — такие условия лучше дробить на UNION ALL, но только если это не ломает семантику. И да, индексы наугад не спасают, если статистика врёт или условие не sargable.

Перед выкладкой в продакшен прогоняйте запрос на реалистичном объёме и смотрите I/O в реальности, а не только время в IDE. Схема простая, но дьявол кроется в статистике: сначала убираем лишнюю работу из плана, и только потом думаем про новые индексы.
Горизонтальное и вертикальное масштабирование: где выигрывает база, а где только бюджет

Коллеги, давайте разберем план выполнения. Вертикальное масштабирование — это добавить CPU, RAM, быстрый диск и получить больше ресурса на одном узле. Горизонтальное — разнести нагрузку по нескольким узлам: реплики, шардинг, кворумные схемы. На бумаге оба подхода «решают проблему», но в эксплуатации у них разная цена.

Вертикаль проще: меньше сетевой сложности, транзакции живут ближе к данным, меньше боли с консистентностью. Но у нее жесткий потолок: один сервер, один отказ, один набор блокировок. Когда упираетесь в I/O или latch contention, добавление ядер уже не лечит. Посмотрим, что тут с I/O в реальности: если диск и память уже в красной зоне, масштабировать CPU — это дорогой способ греть воздух.

Горизонталь дает отказоустойчивость и рост по мере нагрузки, но платите сложностью: распределенные транзакции, лаг репликации, перекосы по ключам, дорогие JOIN’ы между узлами. Самая частая ошибка — начать шардинг до того, как поняли профиль запросов. В продакшене так лучше не делать, и вот почему: потом любая выборка по «неправильному» ключу превращается в обход половины кластера.

Схема простая, но дьявол кроется в статистике: сначала снимите реальные метрики по CPU, RAM, IOPS, p95 latency и блокировкам; потом решайте, что дешевле — усилить узел или дробить систему. Если одна машина еще держит нагрузку с запасом, вертикаль обычно дешевле. Если нужен рост без единой точки отказа — тогда горизонталь, но только после проверки, что модель данных и запросы это переживут.

Золотое правило: сначала мониторинг, потом индексы; потом уже выбор между «мощнее» и «больше».
Автобэкап без проверки восстановления — это не защита, а дорогой ритуал

Коллеги, давайте разберем план выполнения. Резервное копирование должно быть не «скрипт по cron», а связка из трех вещей: снимок, контроль целостности, регулярный restore test. Если хотя бы один этап выпал — бэкап есть только на бумаге.

Что автоматизировать в первую очередь:
— проверку, что копия реально создалась и не пустая;
— контроль времени выполнения и размера: резкие скачки часто сигналят о проблеме;
— уведомления при ошибке, а не тишину в логах;
— ротацию и удаление старых копий, чтобы диск не умер молча.

Посмотрим, что тут с I/O в реальности. Полный бэкап на живой базе без ограничения нагрузки может положить и storage, и репликацию. Лучше заранее задать окно, throttling и приоритет ниже, чем потом ловить деградацию от «безопасности». Для больших баз полезны инкрементальные цепочки, но они требуют особенно жесткой дисциплины: потеряли один кусок — восстановление уже не тривиально.

Самая частая ошибка — никогда не тестировать восстановление. Скрипт может отрабатывать идеально, пока не понадобится вернуть данные. Проверяйте не только «поднялся архив», а полный сценарий: развернуть на чистом стенде, применить журналы, сверить контрольные суммы и открыть приложение на тестовой копии.

Золотое правило: сначала мониторинг, потом индексы. А в бэкапах — сначала restore drill, потом спокойный сон.
Сложный SQL не тормозит сам по себе — его ломают планы, селективность и лишний I/O

Коллеги, давайте разберем план выполнения. У тяжёлого запроса обычно три источника боли: сканы там, где нужен seek; плохая оценка кардинальности; лишние сортировки и хэши на больших объёмах. Схема простая, но дьявол кроется в статистике.

Что проверять первым:
— совпадает ли порядок условий с индексом;
— нет ли функций по колонке в WHERE/JOIN;
— не тащите ли лишние поля в SELECT, если они не нужны;
— не превращает ли OR нормальный план в полный перебор.

Посмотрим, что тут с I/O в реальности: если план красивый, но читает слишком много страниц, оптимизация начинается не с «добавим индекс», а с сокращения входного набора. Фильтры выносят как можно раньше, тяжёлые JOIN'ы — после отсечения мусора, агрегации — только после уменьшения строк.

Ещё один частый провал — CTE и подзапросы, которые читаются как «логика», а исполняются как отдельные тяжёлые этапы. Иногда помогает разнести запрос на шаги с временной таблицей: да, это лишняя запись на диск, но она дешевле бесконечных пересчётов. В продакшене так лучше не делать, и вот почему: без замера можно просто перенести боль из CPU в I/O. ⚠️

Золотое правило: сначала мониторинг, потом индексы. Снимите план, фактическое число строк, чтения, блокировки; только потом меняйте текст запроса. Иначе вы лечите не причину, а форму симптома.
Мониторинг без диагностики — это просто шум: где искать узкое место

Коллеги, давайте разберем план выполнения. Метрика сама по себе ничего не лечит: CPU 90% может быть нормой, а 20% — признаком ожидания на диск или блокировки. Сначала фиксируем базовую картину: нагрузка на CPU, I/O, память, сеть, latency запросов, число активных сессий.

Дальше смотрим не на среднее, а на хвосты. Если p95/p99 растут, а средняя «красивая», значит страдает часть запросов, и именно они роняют SLA. Схема простая, но дьявол кроется в статистике: отдельно проверяем ожидания по типам — lock, io, cpu, temp, network.

Чтобы найти bottleneck, идем от симптома к источнику:
— долгое чтение с диска: ищем тяжелые планы, missing/unused index, full scan;
— блокировки: смотрим цепочки ожиданий и длинные транзакции;
— память: spill в temp, рост сортировок, вытеснение буферов;
— сеть и приложение: не тащим лишние данные и не делаем N+1.

Золотое правило: сначала мониторинг, потом индексы. Иначе получаем «лечили» запрос, а уперлись в чужой lock или в медленный storage. В продакшене так лучше не делать, и вот почему: оптимизация не в том, чтобы ускорить один запрос, а в том, чтобы убрать общий предел системы.
Сложный SQL тормозит не из-за “плохого сервера”, а из-за неудачного плана

Коллеги, давайте разберем план выполнения. У сложных запросов почти всегда ломается одно из трёх: фильтрация слишком поздняя, JOIN раздувает промежуточный набор, сортировка или группировка съедает память и уходит в temp. Сначала смотрим не на текст SQL, а на фактический план: где больше всего строк, где есть Seq Scan, nested loop на больших объёмах и лишние пересчёты.

Дальше работаем по порядку:
— проталкиваем фильтры как можно раньше;
— убираем функции с колонок в WHERE и JOIN, если нужен индекс;
— проверяем, не тащит ли CTE/подзапрос лишние строки;
— заменяем SELECT * на нужные поля, особенно перед JOIN и сортировкой;
— пересматриваем порядок соединений, если одна таблица маленькая, а другая раздута.
Золотое правило: сначала мониторинг, потом индексы.

Если запрос агрегирует данные, ищите место, где можно сократить набор до JOIN. Иногда выгоднее сначала отобрать ключи, потом присоединять детали. Если есть OR по разным полям, план часто деградирует в обход индексов — такие условия лучше дробить на UNION ALL, но только если это не ломает семантику. И да, индексы наугад не спасают, если статистика врёт или условие не sargable.

Перед выкладкой в продакшен прогоняйте запрос на реалистичном объёме и смотрите I/O в реальности, а не только время в IDE. Схема простая, но дьявол кроется в статистике: сначала убираем лишнюю работу из плана, и только потом думаем про новые индексы.