Работая с ORM, избегайте проблемы 1 + N. Это когда вы обращаетесь к ссылочным полям, и база подтягивает сущности штучно, а не разом.

Пример: магазин товаров, сущность Order ссылается на User (кто заказал) и Item (что заказали). Модель выглядит так:

class Order(model.Model):
  status = EnunField(active, cancelled, pending...)
  created_at = DateTimeFiled(now=True)
  user = ForeignField(class=User)
  item = ForeignField(class=Item)

Типичная задача — вывести активные заказы с информацией о клиенте и товаре. Разработчик делает так:

orders = Orders.filter(status=active) \
  .order_by(created_at, desc).all()

Затем он строит таблицу:

for order in orders:
  print order.id, order.user.name, order.item.title

Что произойдет под капотом? Сначала выполнится запрос:

select * from orders where status = 'active'
order by created_at desc;

Тут все в порядке. Однако в цикле, когда происходит обращение к полям user и item, выполняются запросы get-by-id:

select * from users where id = 1
select * from users where id = 2
...
select * from items where id = 100
select * from items where id = 200
...

В среднем запросов будет 1 + 2N. На практике в одном заказе может быть много товаров, а кроме того, возможны подгрузки других сущностей. Скажем, товар хранит ссылку на продавца. Если в таблице должен быть продавец, это будет еще +N запросов.

Именно для таких случаев нужны прошлые советы. Во-первых, запросы должны быть видны в консоли, и разработчик обязан смотреть, что идет в базу. Во-вторых, на эту логику должен быть тест, который считает запросы.

Проблема 1 + N лечится разными способами. Первый — ORM может джойнить сущности, то есть выполнить запрос:

select * from orders
left join users on orders.user_id = user.id
left join items on order.item_id = item.id
where status = 'active'
order by created_at desc;

Такой джоин может нарушить пагинацию по limit/offset, но это страшно. Можно либо не использовать ее вообще, либо взять пагинацию по keyset, либо заменить limit выражением fetch.

Другой способ — вытянуть записи по слоям на уровне приложения. Первый слой — это orders. Как только происходит обращение к user, ORM собирает все user_id и выполнят запрос

select * from users where id in (?, ?, ? ...)

То же самое с items — выгребаются уникальные item_id, и по ним делается запрос с IN.

Некоторые ORM действуют тоньше. Если айдишников много, они выгребают смежные сущности кусками по 10-30. Таким образом, если нужно пройти 100 заказов, мы совершим 4-10 запросов, что не так страшно.

Проблема 1 + N — настоящий бич ORM. Сколько подобных ошибок я исправил — затрудняюсь припомнить (и конечно, совершил сам).

На мой взгляд, в ORM должна быть опция: кидать исключение, если смежные записи читаются штучно. Опция должна быть глобальной, чтобы раз и навсегда запретить подобные вещи. Глядишь, новички стали бы лучше понимать, что вообще происходит.