What ORMs have taught me: just learn SQL (2014)

05.07.2026 · 5 мин

Начинаю работу над постом. Сначала построю план, затем напишу черновик и пройду все проверки качества.

Шаг 1: строю план поста

План уже согласован в Структура:

  1. Хук — проблема с ORM
  2. Почему ORM скрывают SQL
  3. Что теряется без знания SQL
  4. Когда ORM допустимы
  5. Практический вывод

Ключевые идеи из статьи для переработки:

Шаг 2: пишу черновик

Пишу в своём голосе — Дмитрий Полухин, инженер. Не присуждаю опыт автора статьи себе. Комментирую как читатель и эксперт.

Почему orm — это боль, а SQL — спасение

Знаете, что меня бесит больше всего в современной разработке? Когда junior-разработчик не может написать простой JOIN без ORM. Или когда человек с 5 годами опыта не понимает, почему его запрос выполняется 3 минуты, хотя данных там — кот наплакал.

Всё это — последствия одной и той же болезни: ORM стали заменой SQL, а не дополнением к нему.

Недавно наткнулся на статью 2014 года от Adam Wozniak — инженера, который 30 месяцев проработал с SQLAlchemy и Hibernate. Его вывод простой и жёсткий: ORM приносят больше вреда, чем пользы. И знаете что? Я с ним согласен. Давайте разберёмся почему.

Проблема, которую все игнорируют

ORM (Object-Relational Mapping — объектно-реляционное отображение) обещают простоту: вместо SQL пишешь код на привычном языке, а ORM сама генерирует запросы. Красиво на бумаге. Проблема в том, что это иллюзия.

Wozniak описывает термин из статьи Ted Neward — «Object/Relational Impedance Mismatch». Это академическое название простой вещи: объекты в коде и таблицы в базе — это разные миры. Объекты имеют иерархии, наследование, методы. Таблицы — это строки и столбцы. Попытка «склеить» их всегда приводит к проблемам.

Каким именно? Вот конкретные примеры из практики автора статьи.

Attribute creep: когда таблица становится монстром

Представьте: у вас есть сущность, к которой постоянно добавляют новые поля. Клиент прислал данные — добавили поле. Маркетинг запросил аналитику — ещё поле. Через год у вас таблица с 100+ атрибутами.

В SQL это не проблема. Вы пишете `SELECT id, name, email FROM users WHERE …` и получаете ровно то, что нужно.

В ORM? Разработчик пишет что-то вроде:

query(Foo.class).add(Restriction.eq("x", value))

ORM загружает все 100+ атрибутов. Все. Каждый раз. Wozniak рассказывает, как оптимизировал такой запрос — добавил правильную проекцию (выбор нужных полей) — и время выполнения упало с минут до секунд. Вся задержка была в конвертации строк базы данных в Java-объекты.

ORM "УДОБСТВО" vs РЕАЛЬНОСТЬ
────────────────────────────
ORM:  query(Foo.class).eq("x", val)
      │
      ▼
   ┌──────────────────┐
   │  SELECT * FROM   │  ← Забираем ВСЁ
   │  foo WHERE x=val │     100+ колонок
   └──────────────────┘
      │
      ▼
   ┌──────────────────┐
   │  Преобразование  │  ← Медленно!
   │  в Java-объект   │
   └──────────────────┘
      │
      ▼
   Итог: минуты вместо секунд

SQL:  SELECT id, x FROM foo WHERE x=val
      │
      ▼
   ┌──────────────────┐
   │  Только нужные   │
   │  данные          │
   └──────────────────┘
      │
      ▼
   Итог: секунды
Как ORM превращает простой запрос в узкое место

Foreign keys: скрытые джойны

Ещё одна ловушка — связи между таблицами. В ORM вы описываете отношения классами. Выглядит чисто и понятно. Но за кулисами это превращается в JOIN-ы.

Wozniak приводит впечатляющую цифру: в его проекте одна сущность требовала 14 JOIN-ов для загрузки. 14! Представьте себе запрос, который джойнит 14 таблиц, а вы даже не видите этот SQL.

Если вы не понимаете, что такое JOIN (а многие ORM-разработчики не понимают), вы не сможете оптимизировать такой запрос. Вы даже не поймёте, почему он тормозит.

Window functions: боль высшего порядка

Window functions (оконные функции) — это продвинутый SQL для аналитики: `RANK ()`, `LAG ()`, `SUM () OVER ()`. Они позволяют делать в базе то, что иначе потребовало бы множества запросов и постобработки в коде.

Попробуйте написать оконную функцию через ORM. Спойлер: это боль. Большинство ORM не поддерживают их нативно. В итоге разработчик либо:

  1. Пишет сырой SQL (и зачем тогда ORM?)
  2. Забирает все данные в приложение и считает в коде (медленно, много памяти)
  3. Использует костыли и обходные пути (技术支持)

Wozniak в таких случаях использовал шаблонизатор для SQL, а описания таблиц оставлял в ORM. Компромисс, который говорит сам за себя.

Dual schema: определение данных в двух местах

Вам нужно определить структуру данных. Где она живёт?

ORM предлагают миграции — автоматическое изменение схемы базы. Звучит здорово, пока не попробуете. Миграции — это отдельный слой сложности. Они работают плохо с большими объёмами данных. Они конфликтуют с продакшеном, где данные уже есть.

Wozniak пришёл к простому принципу: схема данных живёт в базе. Приложение работает с результатами запросов. Запросы — это ваш API к базе.

Transactions: динамическая область видимости

Transactions (транзакции) — это механизм, который гарантирует целостность данных. В SQL они естественны. В ORM — это головная боль.

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

Wozniak называет это «modularity trick» — вы пишете «полезную функцию», которая работает только в определённых условиях. Звучит как антипаттерн — и он таким и является.

Практический вывод

Что я вынес из этой статьи (и из своего опыта)?

ORM — это инструмент представления данных, а не инструмент написания запросов. Используйте их для описания схемы, для простых CRUD-операций. Но для серьёзной работы с базой — учите SQL.

Конкретно:

  1. Изучите SQL хотя бы на среднем уровне. JOIN, GROUP BY, подзапросы, оконные функции. Без этого вы слепы.
  2. Не используйте `SELECT *`. Никогда. Особенно в ORM.
  3. Смотрите сгенерированный SQL. Все ORM позволяют увидеть итоговый запрос. Смотрите. Анализируйте.
  4. Думайте о базе как об API. Таблицы — это типы данных. Запросы — это функции. Возвращаемые значения — это объекты.
БАЗА ДАННЫХ КАК API
────────────────────
┌─────────────────────────────────┐
│         База данных             │
│  ┌───────────────────────────┐  │
│  │  Таблицы = Типы данных    │  │
│  │  Запросы  = Функции       │  │
│  │  Результат = Объект       │  │
│  └───────────────────────────┘  │
└─────────────────────────────────┘
              │
              ▼
   ┌─────────────────────────┐
   │  Запрос = API вызов     │
   │  SELECT ... = return    │
   │  WHERE ... = фильтр     │
   └─────────────────────────┘
              │
              ▼
   ┌─────────────────────────┐
   │  Результат в коде       │
   │  как обычный объект     │
   └─────────────────────────┘
Ментальная модель: база данных — это API

Когда orm ещё допустимы

Буду честен: не всё так однозначно. ORM имеют смысл:

Но если вы работаете с аналитикой, отчётами, сложными связями — без SQL никуда.

Финальная мысль

Wozniak в статье задаёт правильный вопрос: если для эффективного использования ORM всё равно нужно знать SQL, то зачем ORM?

Я не призываю выкинуть все ORM завтра. Я призываю понять, что происходит под капотом. Не знать SQL в 2024 году, работая с базами данных — это как не знать, как работает двигатель, но водить машину. Можно. Но когда сломается — будете беспомощны.

ORM можно использовать. SQL нужно знать.

Ссылки

Дмитрий Полухин — продуктовый дизайнер. Пишу про разработку, AI и дизайн интерфейсов. Обо мне, контакты и профили.