Базы данных
SQL — декларативный язык. Запрос описывает, какой результат нужен, и ничего не говорит о том, как его получить; выбор способа остаётся за планировщиком, и он вправе выбрать другой план завтра, когда таблица подрастёт. Отсюда первое правило темы — думать множествами, а не строками. Питонист по привычке представляет себе цикл for по строкам, и почти все ошибки на собеседовании растут именно из этой привычки: COUNT(*) после LEFT JOIN считает строку, дополненную NULL; WHERE не видит COUNT, потому что отрабатывает до группировки; GROUP BY схлопывает строки там, где нужна была оконная функция; коррелированный подзапрос молча выполняется заново для каждой строки внешнего запроса.
Вторую половину темы держит то, чего в коде вообще не видно, — транзакции. Драйвер DB-API открывает транзакцию сам, на первом же запросе, и не закрывает её до явного commit(); ORM делает то же. Пока транзакция открыта, СУБД удерживает снимок данных, блокировки и старые версии строк, а VACUUM не может убрать мусор — так безобидный незакрытый курсор превращается в раздувание таблицы. Здесь же живут ловушки, которые стоит назвать сразу: настоящих вложенных транзакций в SQL нет, REPEATABLE READ в PostgreSQL строже стандарта, числа cost в EXPLAIN — не миллисекунды, а обычный VACUUM не возвращает место операционной системе.
Карта темы
- Типы JOIN —
INNER,LEFT,RIGHT,FULLи то, как соединение размножает строки. - Агрегация и GROUP BY — группировка схлопывает строки,
HAVINGфильтрует уже посчитанные группы,COUNT(*)иCOUNT(col)считают разное. - Подзапросы — независимый подзапрос выполняется один раз, коррелированный — по разу на строку внешнего запроса.
- Оконные функции —
OVER,PARTITION BYи рамка дают агрегат, не потеряв ни одной строки. - EXPLAIN и план запроса —
Seq ScanпротивIndex Scan, оценка против факта и почемуcostне измеряется в миллисекундах. - Транзакция и ACID — что именно гарантирует каждая из четырёх букв и чего они не обещают.
- Команды управления транзакцией —
COMMIT,ROLLBACK,SAVEPOINT,SET TRANSACTIONи поведение DDL. - Вложенные транзакции и SAVEPOINT — настоящей вложенности нет; частичный откат делает
SAVEPOINT. - Уровни изоляции — уровень задаётся списком разрешённых аномалий, а
PostgreSQLдаёт больше, чем обещает стандарт. - MVCC и VACUUM — версии строк, мёртвые кортежи, раздувание таблиц и заморозка против переполнения счётчика транзакций.
- Курсоры и DB-API — курсор как указатель на строку результата и серверный курсор для огромных выборок.
- PostgreSQL против MySQL — где различия реальны, а где давно устарели.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
COUNT(*) после LEFT JOIN | Строка, дополненная NULL, считается за единицу — пустая группа даёт 1 вместо 0 |
Фильтровать агрегат через WHERE | WHERE отрабатывает до группировки, и запрос падает с aggregate functions are not allowed in WHERE |
Считать JOIN приклеиванием столбцов | При нескольких совпадениях справа строки слева размножаются, и суммы удваиваются незаметно |
Брать GROUP BY там, где нужна оконная функция | Строки схлопнуты, и показать исходные значения рядом с агрегатом уже нечем |
Читать cost в EXPLAIN как миллисекунды | Это абстрактные единицы, где чтение страницы подряд равно 1.0; реальное время даёт только EXPLAIN ANALYZE |
Писать BEGIN внутри активной транзакции | Вложенная транзакция не возникнет — PostgreSQL отвечает WARNING: there is already a transaction in progress |
Ждать повторяемого чтения от READ COMMITTED | Каждый запрос берёт свежий снимок, и второй SELECT увидит чужой COMMIT внутри вашей же транзакции |
Проверять отсутствие через NOT IN с подзапросом | Единственный NULL в подзапросе делает результат пустым — нужен NOT EXISTS или анти-join |
Значение для собеседований
Тема идёт вторым блоком почти на каждом Python-собеседовании после самого языка, и структура опроса устойчива. Сначала — база на скорость: типы JOIN, что такое транзакция, что означают буквы ACID, какие команды управляют транзакцией. Здесь ждут не определения из учебника, а механизм — «атомарность значит, что при сбое СУБД откатит уже сделанные изменения по журналу», а не «атомарность значит атомарно». Затем дают три-четыре типовые задачи на SQL — дубликаты через GROUP BY … HAVING, клиенты без заказов через анти-join, вторая по величине зарплата, топ по каждому отделу — и смотрят, дойдёте ли вы до оконной функции сами или будете городить коррелированный подзапрос.
Дальше начинается разбор на понимание. Уровни изоляции спрашивают через аномалии — что такое грязное, неповторяющееся и фантомное чтение и какой уровень какую из них запрещает; сильный ответ добавляет, что PostgreSQL реализует REPEATABLE READ снимком и потому попутно закрывает фантомы, а READ UNCOMMITTED у него ведёт себя как READ COMMITTED. EXPLAIN проверяют на умении отличить оценку от факта и объяснить, почему планировщик выбрал Seq Scan. VACUUM — на понимании MVCC — откуда вообще берутся мёртвые кортежи. Типичная ошибка на всём блоке одна и та же — кандидат пересказывает названия («есть четыре уровня изоляции») и не может назвать ни одного механизма под ними, а следующий вопрос всегда именно про механизм.