Объектно-реляционное отображение — Примеры
Пример 1 — Обход, который превращается в запрос на строку
На экране операций перечислены все открытые заказы, а их клиентам уходит напоминание. На вид в коде две инструкции, однако вторая выполняется по разу на заказ, и чем длиннее список, тем медленнее экран. Уберите повторяющуюся инструкцию и покажите доказательство, что она исчезла.
orders = session.all("select * from orders where status = 'open'")
for order in orders:
send_reminder(order.customer.email)
Решение
orders = session.all(
"select o.id, c.email from orders o"
" join customers c on c.id = o.customer_id"
" where o.status = 'open'"
)
for order in orders:
send_reminder(order.email)
# журнал инструкций
# select o.id, c.email from orders o join customers c ... -> 1 инструкция, 214 строк
Связь загружается вместе с исходным запросом, поэтому число инструкций больше не зависит от размера списка, а доказывает это строка журнала: сам код выглядел бы почти одинаково в обоих случаях. Включать журналирование инструкций при разработке — привычка, которая находит такие дефекты до того, как их увидит боевая среда. Обычно ошибка в другом: переносят цикл, а не загрузку, — обход прячется во вспомогательной функции или сериализаторе, и лишний запрос всплывает там, где код экрана о нём и не упоминает.
Пример 2 — Перенос записи внутрь её транзакции
Оформление заказа добавляет строку заказа, резервирует товар и делает запись в реестре. Удаться или провалиться эти три шага должны вместе, но сброс отображателя происходит раньше двух из них, а фиксация — после возврата из функции. Поставьте границу туда, где код её заявляет.
def place_order(cart):
session.add(Order(cart.id))
session.flush() # вставка выдаётся здесь
stock.reserve(cart.items)
ledger.record(cart.total)
# вызывающий фиксирует гораздо позже, после отправки ответа
Решение
def place_order(cart):
with session.transaction(): # сброс ниже происходит внутри этого блока
session.add(Order(cart.id))
session.flush() # вставка выдана, но ещё не зафиксирована
stock.reserve(cart.items)
ledger.record(cart.total)
# фиксация при выходе из блока; сбой выше не сохранит ни одну из трёх
# сессия завершается вместе с запросом, поэтому карта идентичности её не переживает
Явный блок не даёт зафиксировать резерв и запись в реестре без заказа, а сбой до конца блока не оставляет частичного состояния. Ограничение сессии одним запросом важно по той же причине. Живущая долго сессия переиспользует устаревшие объекты и растёт без предела. Чаще всего транзакцию ставят вокруг кода, который уже записал данные, — из предположения, что сброс совпадает с вызвавшей его строкой.