---
title: Django ORM и базы данных
questionDates:
  q-transfer-0217: '2026-10-01'
  q-transfer-0218: '2026-10-01'
  q-transfer-0219: '2026-10-01'
  q-transfer-0220: '2026-10-01'
  q-transfer-0221: '2026-10-01'
  q-transfer-0222: '2026-10-01'
  q-transfer-0223: '2026-10-01'
  q-transfer-0224: '2026-10-01'
  q-transfer-0225: '2026-10-01'
  q-transfer-0226: '2026-10-01'
  q-transfer-0227: '2026-10-01'
  q-transfer-0228: '2026-10-01'
  q-transfer-0229: '2026-10-01'
  q-transfer-0230: '2026-10-01'
  q-transfer-0231: '2026-10-01'
  q-transfer-0261: '2026-10-01'
  q-transfer-0262: '2026-10-01'
  q-transfer-0263: '2026-10-01'
  q-transfer-0264: '2026-10-01'
seo:
  description: >-
    Тема «Django ORM и базы данных» для собеседования Python Developer. Чем
    aggregate() отличается от annotate()? Как MVCC выбирает видимую версию
    строки?
  title: Django ORM и базы данных — Python Developer
---

[Все темы Python Developer](/prep/python-developer)

## Чем aggregate() отличается от annotate()? [#q-transfer-0217]

`aggregate()` завершает QuerySet и возвращает один словарь со сводными значениями. `annotate()` добавляет вычисленное поле каждой строке результата; вместе с `values()` оно задает группировку. JOIN по нескольким связям может размножить строки и исказить суммы, поэтому проверяют SQL и при необходимости используют `distinct`, подзапрос или отдельные агрегации. Агрегаты по пустому набору часто дают `NULL`. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [aggregate и annotate: сводный словарь, значения для объектов и размножение JOIN — документация Django](https://djangodoc.ru/5.0/topics/db/aggregation/)
   :::

---

## Как MVCC выбирает видимую версию строки? [#q-transfer-0218]

PostgreSQL хранит версии строк. Snapshot определяет, какие транзакции считаются видимыми, а служебные `xmin` и `xmax` описывают создание и завершение жизни версии. Обычный `SELECT` обычно не блокирует запись, но записи могут конфликтовать между собой. Старые версии превращаются в dead tuples; `autovacuum` освобождает их для повторного использования, а долгие транзакции мешают очистке. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [MVCC PostgreSQL: снимки данных и чтение без блокировки записи](https://postgrespro.ru/docs/postgresql/18/mvcc-intro)
   :::

---

## Когда нужен SELECT FOR UPDATE? [#q-transfer-0219]

`SELECT ... FOR UPDATE` нужен внутри транзакции для сценария read-check-write: выбранные строки блокируются от конкурирующего изменения до завершения транзакции. Обычное чтение блокировка не заменяет. Транзакцию держат короткой, строки берут в стабильном порядке и задают политику ожидания, например timeout, `NOWAIT` или `SKIP LOCKED` в PostgreSQL, чтобы управлять дедлоками и очередями. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [SELECT FOR UPDATE, блокировки строк и порядок транзакций — PostgreSQL](https://postgrespro.ru/docs/postgresql/18/explicit-locking)
   :::

---

## Как обработать ошибку внутри ORM-сессии? [#q-transfer-0220]

Сессию и транзакцию открывают на четкой границе операции. При ошибке делают rollback до повторного использования сессии, затем освобождают ее через контекстный менеджер или `finally`. Повторяют только классифицированные временные ошибки, например serialization failure или разрыв, и только если операция идемпотентна. Ошибку ограничения данных не лечат слепым retry; ее переводят в понятную доменную ошибку. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Ошибка ORM-сессии: rollback и закрытие ресурса — учебник SQLAlchemy 2.0 ТГТУ, с.21 и 48](https://www.tstu.ru/book/elib1/pdf/2024/EliseevAI2.pdf#page=22)
   :::

---

## Что гарантируют миграции схемы? [#q-transfer-0221]

Миграции дают версионированный граф изменений схемы и воспроизводимый порядок их применения. Они не гарантируют безостановочность, обратимость каждого DDL или корректность преобразования данных. Большую data migration отделяют, делают повторяемой и наблюдаемой. Перед rollout проверяют поведение конкретной СУБД и версии, блокировки и совместимость старого и нового приложения во время поэтапного деплоя. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Миграции Django: версия схемы, зависимости и ограничения разных СУБД](https://djangodoc.ru/5.0/topics/migrations/)
   :::

---

## Как разложить задержку запроса к БД по слоям? [#q-transfer-0222]

Задержку раскладывают на ожидание соединения в пуле, сеть, ожидание блокировки, выполнение плана в БД и передачу или десериализацию результата. Нужны тайминги на каждой границе, slow query log и состояние активных транзакций. `EXPLAIN ANALYZE` подтверждает фактические строки и время операторов, но сам выполняет запрос, поэтому с изменяющими запросами и production-нагрузкой его применяют осторожно. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Профилирование задержки: выполнение SQL, получение строк и обработка ORM — SQLAlchemy](https://pythondoc.ru/docs/sqlalchemy/2.0/faq/performance/#how-can-i-profile-a-sqlalchemy-powered-application)
   :::

---

## Как найти запросы с наибольшей суммарной нагрузкой? [#q-transfer-0223]

В PostgreSQL `pg_stat_statements` сортируют по `total_exec_time`, числу вызовов, среднему времени и чтениям, чтобы отличить редкий медленный запрос от частого дорогого. `pg_stat_activity` показывает текущие запросы и waits, а системные представления блокировок находят очередь. Затем выбранный нормализованный запрос проверяют через `EXPLAIN (ANALYZE, BUFFERS)` на безопасном примере и со свежей статистикой. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Поиск самых дорогих запросов по total_exec_time, calls и чтениям — pg_stat_statements](https://postgrespro.ru/docs/postgresql/18/pgstatstatements)
   :::

---

## Когда Django QuerySet выполняет SQL? [#q-transfer-0224]

Django QuerySet ленив: фильтры строят запрос, а SQL обычно выполняется при итерации, `list()`, `len()`, `bool()`, срезе с шагом и других операциях, требующих результата. После оценки QuerySet кэширует строки для повторного обхода того же объекта. Новый QuerySet или `.all()` может выполнить запрос заново. Методы `exists`, `count`, `update` и `delete` отправляют свои запросы сразу. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Когда выполняется QuerySet: итерация, срезы, len, list и bool — документация Django](https://djangodoc.ru/5.0/ref/models/querysets/)
   :::

---

## Как типизировать модель SQLAlchemy 2.0? [#q-transfer-0225]

В SQLAlchemy 2.0 поля декларативной модели описывают как `Mapped[T] = mapped_column(...)`, а связи как `Mapped[Related]` или коллекцию. `T | None` должно согласовываться с `nullable=True`; это разные уровни, но расхождение вводит в заблуждение. Тип связи отражает кардинальность, а `relationship` не является колонкой. Аннотации помогают анализатору и мапперу, но не валидируют вход API. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Mapped и mapped_column, типизация скалярных полей и связей — учебник SQLAlchemy 2.0 ТГТУ](https://www.tstu.ru/book/elib1/pdf/2024/EliseevAI2.pdf#page=18)
   :::

---

## Как устранить N+1 в Django ORM? [#q-transfer-0226]

N+1 находят по числу SQL-запросов при сериализации списка. `select_related` делает JOIN для одиночных `ForeignKey` и `OneToOne`; `prefetch_related` выполняет отдельный запрос и связывает в Python коллекции и many-to-many. Выбор зависит от кардинальности и объема. Предзагрузка всего графа тоже может быть дорогой, поэтому фиксируют нужные связи и проверяют число запросов тестом или профилировщиком. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [N+1: select_related через JOIN и prefetch_related отдельными запросами — Django](https://djangodoc.ru/5.0/ref/models/querysets/#prefetch-related)
   :::

---

## Когда OFFSET pagination перестает работать эффективно? [#q-transfer-0227]

Глубокий `OFFSET` заставляет СУБД найти и отбросить все предыдущие строки, поэтому время растет. При параллельных вставках и удалениях страницы также получают дубли или пропуски. Для длинной ленты используют keyset pagination: стабильный уникальный `ORDER BY` и условие после последнего ключа, например по `(created_at, id)`. OFFSET остается удобным для небольших административных выборок и перехода к номеру страницы. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Стоимость глубокого OFFSET и пагинация по последнему ключу — Маркус Винанд / Т-Банк](https://habr.com/ru/companies/tbank/articles/485036/)
   :::

---

## Как порядок столбцов влияет на составной индекс? [#q-transfer-0228]

Составной B-tree индекс полезен запросам, использующим его левый префикс; точные правила зависят от СУБД и условий. Порядок выбирают по реальным `WHERE`, диапазонам и `ORDER BY`, а не по одной лишь селективности столбца. Каждый индекс занимает место и удорожает запись. Решение подтверждают планом и статистикой на похожих данных, потому что оптимизатор может предпочесть full scan. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Составные индексы: ведущие столбцы, диапазоны и skip scan в PostgreSQL](https://postgrespro.ru/docs/postgresql/18/indexes-multicolumn)
   :::

---

## Как безопасно выполнить raw SQL в Django? [#q-transfer-0229]

В Django значения передают отдельными параметрами через `raw()` или `connection.cursor()`, не склеивают строку и не подставляют кавычки вручную. Имена таблиц и столбцов параметрами не становятся, их выбирают из разрешенного набора и корректно quote-ят средствами backend. Запрос помещают в нужную транзакцию, документируют диалект СУБД, проверяют план и покрывают интеграционным тестом. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Параметризованный raw SQL и connection.cursor в Django](https://djangodoc.ru/5.0/topics/db/sql/)
   :::

---

## Почему сервису нужен ограниченный пул соединений? [#q-transfer-0230]

Каждое соединение расходует память и работу БД, а тысячи конкурентных запросов создают contention вместо роста throughput. Лимит пула рассчитывают из общего бюджета БД на все реплики и фоновые процессы. Когда пул занят, ожидание создает управляемый backpressure; у него должен быть timeout и метрика. Соединение всегда возвращают в пул, завершая или откатывая транзакцию, иначе утечка быстро исчерпает лимит. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Ограниченный пул как защита базы: ожидание, timeout и возврат соединений — SQLAlchemy](https://pythondoc.ru/docs/sqlalchemy/2.0/errors/#queuepool-limit-of-size-x-overflow-y-reached-connection-timed-out-timeout-z)
   :::

---

## Когда ALTER TABLE переписывает большую таблицу? [#q-transfer-0231]

Цена `ALTER TABLE` зависит от СУБД, версии и конкретной операции. Смена типа, materialized default, проверка существующих строк или некоторые изменения хранения могут переписать таблицу; создание индекса и validation долго читают данные и берут блокировки разной силы. Перед production rollout проверяют документацию своей версии и репетицию на копии, используют совместимые фазы expand-migrate-contract и готовят откат приложения. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Когда ALTER TABLE требует перезаписи или сканирования — PostgreSQL 18](https://postgrespro.ru/docs/postgresql/18/sql-altertable)
   :::

---

## Какие риски есть у ON DELETE CASCADE? [#q-transfer-0261]

`ON DELETE CASCADE` удобно поддерживает ссылочную целостность, но одно удаление может запустить большую и неожиданную цепочку, долго держать блокировки и раздувать транзакционный лог. Внешние ключи дочерних таблиц обычно индексируют, чтобы поиск зависимостей не сканировал их целиком. Для важных сущностей часто нужна явная бизнес-операция, аудит или soft delete. Массовое удаление репетируют и ограничивают пакетами. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Выбор ON DELETE CASCADE и индексы дочерних внешних ключей — PostgreSQL](https://postgrespro.ru/docs/postgresql/18/ddl-constraints)
   :::

---

## Когда GenericForeignKey хуже обычного ForeignKey? [#q-transfer-0262]

`GenericForeignKey` хранит пару `content_type` и `object_id`, позволяя ссылаться на модели разных типов. Цена: обычный внешний ключ БД не гарантирует существование цели, каскады и запросы сложнее, `select_related` не работает как для нормальной связи. Если набор целей известен, отдельные `ForeignKey`, общая таблица или явная модель связи обычно надежнее. Полиморфизм оправдан только реальным доменным требованием. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [GenericForeignKey: полиморфная связь, ограничения запросов и удаления — Django](https://djangodoc.ru/5.0/ref/contrib/contenttypes/)
   :::

---

## Какую ответственность должен иметь ModelViewSet? [#q-transfer-0263]

`ModelViewSet` связывает стандартные CRUD actions с queryset, serializer, permissions и router. Он должен адаптировать HTTP-вход, выбрать данные, вызвать прикладную операцию и сформировать ответ. Длинные транзакции, правила домена и интеграции лучше вынести в сервисы или модели с ясной границей. При этом не нужна абстракция ради абстракции: простая проверка, относящаяся только к endpoint, может остаться во ViewSet. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [ModelViewSet: HTTP actions, queryset, сериализатор и маршруты — selfedu](https://proproprogs.ru/django/drf-viewsets-i-modelviewset)
   :::

---

## Когда SerializerMethodField создает N+1? [#q-transfer-0264]

`SerializerMethodField` вызывает `get_<field>` для каждого сериализуемого объекта и является read-only. Если метод каждый раз обращается к непрефетченной связи или выполняет aggregate, список создает N+1. Данные заранее добавляют через `select_related`, `prefetch_related` или `annotate`, а метод читает подготовленное значение. Число запросов проверяют на списочном endpoint, потому что один объект проблемы не показывает. Вывод подтверждают фактическим SQL, числом запросов и планом на данных, похожих на production.

:::note[Ссылки для изучения]

1. [Скрытые запросы в SerializerMethodField и проблема N+1 — опыт Самолета](https://habr.com/ru/companies/samolet/articles/794740/)
   :::
