С чего все началось
Во время выполнения очередного проекта мне пришлось работать с Битрикс ORM, при этом параллельно в системе был инстанс Laravel и его EloquentORM. Две разные ORM работали с единой базой данных. Не буду вдаваться в причины, по которым был выбран такой подход, и воздержусь от его оценки. Суть в том, что мне приходилось одновременно работать с двумя принципиально разными системами. Этот опыт привел меня к фундаментальному выводу: ORM — не для меня.

Проблема ORM
ORM во всех фреймворках одновременно и похожи, и различны. Основные различия — в синтаксисе: одни и те же ключевые слова в разных ORM используются по-своему. Запомнить все нюансы невозможно. До сих пор попытки сделать JOIN в ORM Битрикса вызывают у меня большие трудности. При этом у Битрикс ORM есть определённые ограничения, о которых желательно знать заранее.
ORM сам по себе неплох, но только для простых операций: его удобно использовать для быстрого обновления и получения данных из таблиц, настраивая модели данных (мапперы) и работая с ними как с объектами в языке программирования. Но часто использование ORM становиться мучительным.
Какие аргументы используют сторонники ORM подхода?
Защита от SQL-инъекций
Объектно-ориентированный подход
Абстракция от БД
Проверка типов
Миграции схемы БД
Кэширование
Разберем каждый аргумент по порядку.
Защита от SQL-инъекций — это параметризированные запросы. Технология, при которой каждый параметр, динамически вставленный в SQL-запрос, экранируется. Практически везде, где есть драйверы для работы с базой данных, есть параметризированные запросы.
Объектно-ориентированный подход. Во время разработки создаем классы и получаем объекты, которые соответствуют таблицам в базе данных. Действительно, бывает удобно использовать связи между объектами, избегая JOIN конструкций, но часто это дополнительно усложняет код, особенно если ты не очень знаком с фреймворком — легко заблудиться в настройках моделей. Альтернативное решение — использование репозиториев, где запрос указывается в чистом виде, а на выходе дается заранее подготовленный объект.
Абстракция от БД. Этот аргумент используют, подразумевая независимость кода от используемой базы данных. Например, во время разработки ты можешь пользоваться SQLite, а на production подключиться к Postgres и оставить код в том же состоянии. Либо при росте проекта перейти к более профессиональному решению, когда SQLite покажется недостаточно функциональным. У меня был опыт перехода рабочего проекта с SQLite на Postgres и это оказалось совсем непросто. Есть большая вероятность конфликта типов и ваш ORM не всегда сможет это объяснить в своих Exception так, чтобы вы поняли с первого раза.
Проверка типов. Когда мы описываем модель, то в коде прописываем типы. ORM валидирует данные на этапе компиляции или интерпретации, сообщает нам об ошибке в случае несоответствия. Но у базы данных уже встроены типы и мы так или иначе можем получить ошибку во время вставки. Драйвер базы данных часто сообщает об этом, нужно лишь поймать Exception. При этом, например в Laravel, валидацию принято внедрять на уровне контроллера.
Миграции схемы БД. На самом деле, миграции имеют определенную ценность в командной разработке, хотя часто работа с миграциями может нести в себе скрытые проблемы. Например, у меня возникали проблемы с переименованием колонок в Django. Механизм генерации миграций устроен так, что он парсит модель и на ее основе генерирует код, который затем выполняется в базе данных. Этот код не всегда соответствует задуманному решению. Даже ручное изменение базы данных можно сделать понятнее и быстрее.
Кеширование не так уж и сложно для понимания. Если оно необходимо, всегда можно настроить кеширование конкретного запроса, либо обернуть вызовы по требованию.
Ну а когда речь заходит об агрегатных функциях или сложных запросах с использованием вложенности, условий или других вспомогательных функций движка базы данных, то всё становится совсем плохо. Если использование JOIN еще воспринимается нормально после нескольких часов практики, то сложные надстройки, требующие нетривиальных запросов, выносят мозг. Каждая ORM реализует эти возможности по-своему.
Каждый раз, когда приходится работать с ORM, при переходе от одного фреймворка к другому задаешься вопросом - “а зачем все это нужно, когда с SQL работать в разы проще и понятнее?”. Стоит однажды разобраться с SQL, используя популярные запросы, и моментально получаешь ключ к данным, отбрасывая необходимость в велосипедах на ORM.
Давайте я проиллюстрирую это на одном конкретном примере: представим, что нам нужно выбрать всех пользователей, у которых есть хотя бы один завершенный заказ, и посчитать количество их активных заказов.
Посмотрим, как эта задача решается на разных инструментах.
Примеры использования ORM
Для начала, рассмотрим Laravel с его Eloquent ORM. Наш запрос можно описать несколькими способами, используя ORM или Query Builder подход:
Вариант 1. Использование Laravel Eloquent ORM
$users = User::whereHas('orders', function($query) { |
Для выполнения этого кода необходимо, чтобы были объявлены отношения между таблицами в моделях.
А вот так мы, используя Query builder, будем строить наш запрос в том случае, если отношения не определены:
Вариант 2. Использование Query Builder Laravel - почти SQL
В целом, выглядит не плохо.
Теперь попробуем использовать другой популярный фреймворк и посмотрим, что есть у Django:
Пример запроса в Django ORM с применением аннотаций
from django.db.models import Count, Q |
А вот другой вариант, с агрегацией - чуть сложней:
from django.db.models import Count, Q |
Вроде выглядит неплохо, когда этот запрос уже написан и протестирован — действительно. Когда ты пишешь такие запросы ежедневно — возможно. Но мне к сожалению, это дается нелегко.
А вот и главная боль — использование Битрикс ORM:
$users = UserTable::query() |
В Битрикс есть несколько способов для построения запросов, но все они у меня вызывают нервный тик, особенно во время их написания: чтобы просто присоединить таблицу, тебе нужно объявлять сущности, runtime-поля, ReferenceField'ы... Код “распухает” до неузнаваемости, и на третьем экране ты уже забываешь, что вообще хотел сделать. Приходится разбираться с каждой абстракцией, заложенной разработчиками фреймворка.

Чистый SQL запрос
А теперь, я хочу показать, что делают все вышеописанные конструкции на самом деле и как это могло бы быть, если бы я знал базовый SQL до того, как стал возиться с этими ORM и Query Builder. Нативный, продуманный до мелочей, лаконичный синтаксис SQL:
Выполняем его в raw конструкции и получаем те данные, которые ожидаем. Моментально избавляем себя от необходимости проводить часы погружения в документацию к любимому фреймворку. После ORM монстров, этот код кажется "глотком свежего воздуха". Один раз поняв суть построения запросов, ты можешь применить это знание в любом проекте, на любом языке. Как человеку, который работает с PHP, Python, JS, Go, мне это невероятно важно.
Понимание того, как писать запросы, приходит после прохождения базового курса по SQL запросам. Будет проще, если вы найдете интерактивный тренажер по SQL, что ускорит обучение.

Инструментарий
Любопытно, что, начав активно пользоваться SQL, я стал гораздо лучше понимать, где и с какими данными я работаю. Я использую DBeaver для просмотра таблиц: там же пишу запрос, сразу вижу его результат и при необходимости оптимизирую. Интерфейс программы дружелюбен и заточен именно под работу с базами. Кроме того, полезные сложные запросы можно сохранять и использовать повторно.
Эта же программа позволяет обходиться без консоли при импорте и экспорте данных — можно выгружать результаты даже в CSV. Не знаю, как для кого, а для меня это стало настоящим открытием и лайфхаком. Вероятно, этому где-то учат, но мне пришлось провести немало дней за тупыми скриптами для выгрузки в CSV, не имея полной картины данных и пытаясь ориентироваться лишь на определение моделей в коде.
Надеюсь, мой опыт будет полезен новичкам и поможет им не тратить время на бесконечное изучение абстракций: сразу начать работать с мощными и понятными инструментами, выполняя задачи быстрее и эффективнее.
Вывод
ORM - хороший инструмент для простых CRUD операций. Но для сложных запросов и работы с большими объемами данных, чистый SQL оказывается более мощным, понятным и предсказуемым.
Инвестируйте время в изучение SQL — это окупится многократно в любом проекте, на любом стеке технологий.
Используйте удобные и эффективные инструменты для работы с данными, такие как Dbeaver, чтобы видеть и понимать свои данные напрямую.
Ну, а если у Вас есть аргументы посильнее, приглашаю поспорить в комментариях.
Комментарии (24)

user-book
06.11.2025 07:49ORM появился как быстрый и безопасный способ работы с базой (без привязки к конкретной) для людей низкой квалификации.
Если у вас не что-то свое православное, то настоятельно рекомендую с ORM перейти на генераторы SQL. То есть вы все так же работаете через классы и методы, а SQL-запрос уже собирается через него. Потому как руками писать SQL на долгой дистанции не безопасно и сложноотлавливаемо.

itatarchenkoru Автор
06.11.2025 07:49Я как раз и столкнулся с проблемой, когда моя низкая квалификация усиливалась отсутствием подготовки к работе с SQL. Я обращаю внимание на то, что изучение ORM идет впереди понимания SQL, тем самым делая работу разработчика невыносимой. Хотя писать SQL запросы не такая большая проблема, если потратить немного времени на теоретическую подготовку и тренировку для работы с чистым языком запросов.
Вы говорите про генераторы SQL, но его нельзя тестировать абстрагируясь от кода в котором они работают. С чистым языком запросов все легко писать в DBeaver и аналогах (хоть в консоли), без необходимости дополнительно настраивать чистое окружение для тестирования результатов запроса.
user-book
06.11.2025 07:49как раз генераторы как и ORM отлично заворачиваются в тесты мокаясь
зато с чистым языком очень легко где-то символом провафлится и если сильно повезет то оно может пройти все тесты и вылетать только когда сойдутся звезды, то есть на реальном пользователе поймать пограничный случай
как плюс - перекатка на другую базу как и с ORM не требует лишних телодвижений

megadrugo2009
06.11.2025 07:49может пройти все тесты и вылетать только когда сойдутся звезды
Напишите тест который выполняет запрос в базу.
Это может быть e2e тест, где проверяется функционал с sql запросом, по методу черного ящика.

user-book
06.11.2025 07:49вы как будто никогда работу "в живую" не видели)
можно все покрыть тестами. можно сразу писать нормально без ошибок. все можно, вот только реальность полна разочарований потому и надо со старта предвосхищать будущие проблемы

Tishka17
06.11.2025 07:49Можно пруф про то, что ORM появился для этого? Насколько я вижу, всяике паттерны в духе data mapper, metadata mapping появились вообще для другого - упрощения работы в сложной системе, где много сущностей

user-book
06.11.2025 07:49так вы сами ответили на свой вопрос)
Упрощение работы, все верно. Что бы было проще и безопаснее работать со сложными сущностями. Что бы было меньше требований к линейному разрабу (как непосредственно к нему, так и к результатам его труда)

Tishka17
06.11.2025 07:49Прошу прощения, что плохо выразил мысль. Под упрощением я имео ввиду не писать в сотый раз update или insert или не следить за тем чтобы имена таблиц после as не повторялись при джойнах на одну и ту же. Это не требует квалификации, это требует аккуратности. ORM же наоборот повышает требования к квалификации сотрудника - теперь ему мало знать SQL, надо ещё разбираться в дополнительной технологии.

JBFW
06.11.2025 07:49Прав во всем.
ORM хорошо для статичных случаев, когда разрабатывается некое решение раз и навсегда , реализуется и высекается в граните навеки.
Если начинается развитие, доработки и переделки - ORM превращается в прокрустово ложе, простые задачи приходится реализовывать через пень-колоду, сложные - писать костыли для промежуточной обработки данных, и в итоге - выбрасывать ORM из проекта.

Kerman
06.11.2025 07:49Ровно наоборот. Когда схема данных развивается и меняется, ORM автоматически подхватывает изменения в объектах и запросах. А когда всё сделано на сырых sql запросах - вперёд искать по всему проекту, где используются изменённые поля и менять. А самое подлое - то, что ещё не найдено падает в рантайме. Потому что sql запрос в кавычках всегда будет валидным для вашего яп. А в какой момент исполнение кода наткнётся на пропущенный кусок, где вы забыли user_id поменять на manager_id - никто не знает.

itatarchenkoru Автор
06.11.2025 07:49В моих проектах я с такими трудностями не встречался. Я обычно разрабатываю ближе к паттерну репозиторий. Стараюсь следить за движением данных, а чтобы не было неожиданностей, покрываю реализацию тестами, которые гарантируют, что я ничего не пропустил.
Я согласен с вашим подходом, но только если работать постоянно на одном стеке, используя мышечную память во время написания запросов. Мой опыт - работа в разных системах, где SQL просто экономит и время, и нервы.
Kerman
06.11.2025 07:49Стараюсь следить за движением данных
Я такой подход слышал. Называется "нужно просто одновременно аккуратно менять код и базу данных и не ошибаться". Только это не работает )
Работает code-first, работает db-first и ещё одна дичь, про которую я написал статью.
покрываю реализацию тестами, которые гарантируют, что я ничего не пропустил
Тесты точно также начинают врать при изменении структуры данных. Да и тесты не могут дать гарантию, что данные маппятся правильно. Особенно при добавлении новых. Потому что нет тестов, которые проверяли бы соответствие тестов структуре. Вот ORM может дать гарантию, а тесты - нет.

megadrugo2009
06.11.2025 07:49А самое подлое - то, что ещё не найдено падает в рантайме.
Это проблема отсутствия e2e или интеграционного теста.
Когда схема данных развивается и меняется
В больших компания строго запрещается ломать обратную совместимость в схемах БД.

JBFW
06.11.2025 07:49Это как раз решается путем выноса логических блоков в библиотеки.
То есть вы не дергаете sql-запросы по всему коду, и orm тоже, вместо этого работаете с функциональными объектами и их свойствами: например, принимаете товар на склад или создание клиента - а как именно создаете - зашито в библиотеке.
Если угодно - это тоже можно называть ORM, но на более высоком уровне, это уже не записи в базе, это объекты предметной области: товары, клиенты, грузовики, сервера - то с чем вы работаете.
ORM в таком случае просто ни к чему.

itatarchenkoru Автор
06.11.2025 07:49Спасибо. Как раз с этим сталкивался неоднократно. Особенно остро на это смотришь, когда занимаешься агрегированием данных, которые на SQL делаются гораздо быстрее, а главное понятнее.

impwx
06.11.2025 07:49Проблема скорее не в ORM как концепции, а в том, что ее реализация на PHP или Python получается просто дико вербозная. Посмотрите ради интереса на LINQ2DB в .NET или Ktorm в Kotlin - там гораздо красивее получается, т.к. лаконично и при этом никаких магических строк

vanyasokolov
06.11.2025 07:49Всё зависит от того, над какими проектами работать предстоит, для меня ORM это инструмент, который ускоряет разработку, потому что нет ничего лучшего, чем работать с БД через объекты, без написания сырых скуль-запросов, но при масштабировании проектов это может сказаться на общей производительности, поэтому нужно ситуативно решать использовать ORM или нет

ponikrf
06.11.2025 07:49ORM это не только строитель запросов и он нужен не только для простых CRUD операций.
Сама по себе объектная абстракция позволяет вам сложить логику обработки данных внутри модели. Например, перед созданием модели или после редактирования и тп. А так же добавить методы для работы с конкретной моделью. Ну это только скажем так самая вершина ORM.
ORM модель это точка входа в модель. Если вы настроили ее правильно и используете только ее - это вам гарантирует правильность работы вашей системы с данными которые она обслуживает. Напротив я бы сказал, что чем сложнее проект и масштабнее, тем больше там нужна ORM. Потому что логики относительно каждой модели становится все больше, и хранить это все просто в SQL - моделях - очень сложно.
А то что вы перевели часть запросов на простой sql - это вполне себе норма. Даже когда используют ORM - не всегда сложные запросы делают через нее. Но говорить что ORM ненужна - это с вашей стороны - недостаток опыта.
Ничего, все придет.

Tishka17
06.11.2025 07:49Если у вас две разные системы ходят в одну БД - это уже потенциальная проблема. Если есть хоть какая-то бизнес логика, а не простые crud или (не простой) поиск, у вас появляются два конфликтующих набора бизнес правил. Если же там только чтение - что мешало сделать http api вместо прямых запросов в бд?
Что касается, ORM - не все они одинаковы. Есть условно два подхода - data mapper и active record. Но в целом задача ORM - дать средства для синхронизации состояния в памяти приложения с базой данных. Задачи, связанные с тупой отдачей данных и построения для этого сложных выборок они не обязаны решать. При этом, если мы берём sqlalchemy - у него есть билдер запросов, который как раз помогает собирать динамический сложный sql. А если берём другую ORM - Django, то у неё свой подход и если вы хотите загружать что-то, сильно не совпадающее с сущностями бизнес логики, у вас начинаются сложности.
Моё мнение тут:
Избавьтесь от двух разных технологий работающий с одной БД. Обеспечьте стабильное межсервисное API
Разделите задачи изменения состояния системы и просто отдачи среза данных. Выбирайте, нужен вам ORM или нет для каждой из них
Среди ORM тоже есть варианты, посмотрите что есть.

panzerfaust
06.11.2025 07:49SQL vs ORM
Некорректное противопоставление. Object-Relational Mapping в системе в любом случае присутствует в каком-то виде, если там есть БД и объекты. Другой вопрос: надо ли подключать к проекту комбайн типа хибера, чтобы с этим работать. Мы свой микрофреймворк написали под Vert.x.

Kerman
Для того, чтобы работать с ORM, надо знать SQL. Потому что ORM это не замена SQL.
А самое главное, в ваших примерах почему-то стыдливо пропущено раскладывание результата по полям объектов. Сравнивается запрос в sql синтаксисе, который надо ещё экранировать, отправить в заранее подготовленную команду, получить ридер и прочитать его в объекты с готовой конструкцией $users = UserTable::query().
А ведь ORM - это в первую очередь Object-Relational Mapper.
itatarchenkoru Автор
Спасибо за комментарий. Я стремился дать примеры построения запросов, а не дальнейшей обработки результата.
XelaVopelk
Это очень критично, когда работаешь с нагруженной системой. Если ты пишешь на sql тебе надо знать, как то что ты делаешь будет, мапиться на планы запросов (конкретной СУБД, т.к. у каждой свои нюансы).
Если ты работаешь с орм, когнитивная нагрузка резко повышается: ты должен сначала осознавать как то, что ты пишешь, будет мапиться в запрос, который потом будет мапиться в план запроса.
Усложнение приводит к тому, что разработчики с недостаточной квалификацией начинают воспринимать происходящее в БД слое как "магию": "Я вот жахнул и вот такое получилось. позовите ДБА, пусть сделает индекс какой".