Разработка софта всегда была поиском баланса между разными аспектами, вроде скорости разработки (как быстро ты выкатываешь новые фичи), производительности приложения, потребления им памяти, красотой интерфейса и отполированностью логики.
Почему важна скорость разработки
В большинстве случаев скорость разработки важнее, чем производительность и потребление памяти. Производительность бывает достаточной, потребление памяти бывает приемлемым для выполнения задач приложением, а вот от скорости разработки напрямую зависит прибыльность бизнеса, поэтому скорости разработки никогда не бывает мало. От неё зависит, сколько бизнесу придётся потратить на этап разработки приложения, сколько времени придётся проводить под давлением рисков. Возможно, это приложение для рынка и конкуренты могут успеть раньше, тогда ничего критичнее сроков просто нет.
Конечно, приложение, которое еле двигается и которым невозможно пользоваться не выиграет конкурентную борьбу. Точно также как и приложение, требующее слишком много памяти для своей работы. Но если идёт речь об ускорении приложения на 20% или экономии пары-тройки месяцев разработки, то выбор очевиден. Эти 20% ускорения может никто и не заметит, а вот дыру в бюджете не заметить очень сложно.
Как ORM делает разработку быстрее
ORM - это как раз инструмент, экономящий время разработки. Но за счёт чего?
В первую очередь, ORM делает код короче. Он прячет "под капотом" не интересные никому тонны кода, переносящего значения из колонок таблицы в поля класса и назад. Это ещё даёт типобезопасность. Тип поля в классе всегда будет соответствовать типу данных колонки.
Конечно, типобезопасность касается только статически типизированных языков, но подсказки и автодополнение от IDE работают для всех. С автодополнением работать не только приятнее, но и ошибок меньше. И тоже быстрее.
Так как кода просто меньше, он быстрее пишется и он быстрее читается. Второе важнее для больших проектов, где ограничением является контекстное окно биологической нейросети. Разбивка кода по уровням и функционально ограниченным функциям - это отдельный вид искусства, но скрытие технических подробностей - отличный инструмент для мастеров такого искусства.
ORM позволяет работать на более высоком уровне абстракции. Он берёт на себя работу с полями и выдаёт наружу объекты. Можно взять общую часть запроса и модифицировать её для нескольких частных случаев, например используя полиморфизм. Можно добавлять условия в запрос в зависимости от того, что хочет пользователь вашего приложения. С raw SQL такое сделать гораздо сложнее.
ORM менее подвержен ошибкам. Легко ошибиться в одном из тридцати полей, набирая запрос руками. ORM не ошибётся. Более того, он автоматически поправит ваш запрос, когда вы добавите новые поля, а старые переименуете. Он не забудет. Он поменяет имя поля везде, где оно использовалось. Это очень важный плюс на долгой дистанции, потому что убирает целый пласт ошибок, характерных для человека, а значит экономит уйму времени на их поиск и исправление.
ORM значительно упрощает рефакторинг данных. Это очень недооценённая возможность маппера. Вам будет гораздо проще приводить в порядок схему данных с ORM, чем без него. Очень сложно вывести конкретные метрики, насколько рефакторинг помогает в дальнейшей разработке, но это может дать ускорение процесса разработки в десятки раз, особенно на длинной дистанции.
Да, новый слой абстракции добавляет сложности к кривой вхождения в проект. И если у вас маленький проект с десятком таблиц, которые почти не меняются, то вам, скорее всего, ORM не нужен. Он нужен для управления высокой сложностью проекта, а в малых проектах этой сложности просто нет и ORM даст только прирост сложности за счёт нового слоя абстракции.
Влияние на производительность
В начале статьи я говорил, что разработка - это поиск компромиссов. Означает ли, что внедряя ORM мы потеряем в быстродействии и потреблении памяти? Вовсе нет. Давайте рассмотрим ситуацию комплексно.
Конечно, ORM добавляет какой-то оверхед на составление запросов и маппинг данных. Но давайте честно, в сравнении с временем I/O операций базы данных этот оверхед совершенно незначителен. А значительно то, что за счёт меньшего объёма кода и более лёгкой алгоритмической оптимизации работа с ORM может быть в разы быстрее работы с чистым SQL. Да, SQL тоже можно оптимизировать. Только сложнее увидеть всю картину целиком, да и менять страшно. Может же что-нибудь сломаться. Поэтому стараются лишний раз не трогать и не оптимизировать. И получается парадоксальная ситуация - SQL работает чуть быстрее, как только он написан, но намного медленнее после множества итераций разработки.
Меня иногда упрекают в том, что в системах с ORM очень легко наворотить лишних запросов, особенно если с ней работают джуны, не знающие SQL. Так вот, для работы с ORM нужно знать SQL и надо понимать, в какой запрос превратится выражение из нативного языка разработки. Потому что ORM - это не замена SQL. Он работает поверх языка запросов, добавляя новый слой абстракции, но не избавляет от необходимости знать и понимать нижележащие слои.
Потребление памяти
С потреблением памяти тоже не всё так просто.
Идеология реляционной базы данных подразумевает, что ответ на запрос будет плоской таблицей. Допустим, мы возьмём большую табличку, где 100+ колонок (например orders) и сделаем выборку один-ко-многим с маленькой таблицей на 5 полей (например positions). Если мы будем хранить ответ "как есть", то на каждую позицию мы будем хранить копию всех 100+ полей из первой таблицы. Здесь перерасход памяти может быть и десяти- и стократным. Хороший ORM упакует записи из второй таблицы в единственный экземпляр из левой таблицы.
Без заранее известной информации о типах данных вашему приложению придётся хранить данные в обобщённом виде, указывая тип для каждого значения. Для разработчиков на динамически типизированных языках это в порядке вещей, но в статически типизированных это называется "боксинг" и приводит к перерасходу памяти. Int32, будучи в подобной структуре, занимает 8 байт на стеке, плюс 24 байта в куче (x64 clr). ORM раскладывает данные по типизированным полям, сокращая в разы потребление памяти.
Entity Framework
EF стоит в ряду ORM особняком и далеко не все его возможности доступны в других ORM. Я вроде бы это понимаю, но я пишу эту статью, как прожжёный дотнетчик, представляя себе именно его возможности, где есть грациозное решение N+1, есть LINQ и много чего другого.

Тем не менее, даже у прокаченного EF есть свои недостатки. И они скорее касаются смежных с маппингом аспектов применения. Это синхронизация структуры, тулчейн и различные окружения. Рассмотрим подробнее.
Синхронизация структуры
Самая первая задача любого типа ORM - обеспечить синхронность структуры базы данных и структуры объектов приложения. В соответствии с этой структурой колонки таблицы загружаются в поля и потом обратно. Собственно, это и есть маппинг. Для того, чтобы это корректно работало, где-то нужно брать "оригинал" структуры на этапе написания приложения. Это может быть база данных (DB-first), код (code-first) или отдельная модель (model-first). Code-first проецирует структуру из кода в базу данных. Это удобно, когда есть несколько окружений, но ломается, когда есть несколько независимых сервисов, где каждый пытается подстроить БД "под себя". Его синхронизация сложная, потому что требует загрузки существующей структуры базы данных, корректного сравнения моделей и сложного механизма генерации DDL. Этот подход выходит за рамки статьи, поэтому я его рассматривать не буду.
DB-first и model-first используют кодогенерацию сущностей для синхронизации структуры. Это значительно проще, нужно всего лишь загрузить структуру из базы данных (или взять файл модели в случае model-first) и создать заново файлы сущностей в проекте.
Я заметил, что задача синхронизации структуры данных имеет свои особенности в каждом проекте. И часто возникает необходимость дорабатывать синхронизацию под свой проект, но обычно ORM имеет встроенный генератор моделей для кода, который так просто не исправить. Например, вам, как и мне, хочется, чтобы комментарии из базы данных попадали в файл модели, чтобы была единая внутрикодовая документация. Или вы используете связи из разнотипных полей, а может даже связи многие-ко-многим через текстовые поля, что нельзя сделать внешним ключом в самой базе данных. Или у вас особенная система работы с enum и вам нужно приводить integer в свой собственный в коде.
Эту негибкость ORM часто ругают и справедливо. Я эту проблему решил с помощью генераторов с открытым исходным кодом. Вы можете взять готовый генератор с лицензией MIT и модифицировать его под особенности своего проекта. Это проще, чем писать свой генератор с нуля, потому что структуру данных подготавливает для генератора OrmFactory. Этот инструмент работает напрямую с базой данных и умеет сохранять структуру в файл проекта. Его философия в том, что он предоставляет интерфейс для проектирования схемы данных с возможностью синхронизации окружений с кодовой базой, оставляя ORM только ту работу, с которой он справляется лучше всего - маппинг.
Ещё одно неприятное следствие синхронизации схемы - это отсутствие возможности поменять имя поля в сгенерированном коде. Точнее, поменять-то можно, но оно опять вернётся к первозданному виду при следующей генерации. Это может быть вопросом вкуса, а может быть необходимостью для рефакторинга, когда имена в БД трогать нельзя, но надо назвать как-то человечнее в коде, а может быть суровой необходимостью. Например, в C# имя поля не может совпадать с именем класса. А в БД имя колонки с именем таблицы - запросто. И вот что делать?
Я уже не говорю про такую простую вещь, как приведение полей из snake_case в PascalCase, который чаще всего используется в проектах. Не все ОРМ позволяют автоматически конвертировать кейс-стайл имён.
Собственно, эти вопросы я тоже решил. Когда есть отдельный инструмент, предназначенный для работы именно со схемой данных и связыванием системы воедино, становится немного проще жить.
Спасибо, что дочитали. Всем Пше Згыр!
Комментарии (29)

Severus1992
05.12.2025 11:48Вот очередная статья про ORM и опять речь про маппинг объектов. Но обходят стороной самый главный отличительный функционал ORM - Change Tracker. То, собственно, благодаря чему и происходит значительное сокращение времени разработки.

totsamiynixon
05.12.2025 11:48Согласен. Я писал об этом в данном комментарии к другой статье. https://habr.com/ru/articles/926214/comments/#comment_28553346

Kerman Автор
05.12.2025 11:48Просто в моём понимании ORM - это object-relational mapping. А про трекер я что-то забыл совсем. Он мне кажется таким естественным, что о нём и упоминать не надо. Вроде как у всех так должно быть.

megadrugo2009
05.12.2025 11:48Я сторонник чистого sql.
Но если мне придется делать очередной CRUD, то буду использовать ORM. ИМХО он только для этого и годится.

YokoLit
05.12.2025 11:48ORM менее подвержен ошибкам. Легко ошибиться в одном из тридцати полей, набирая запрос руками.
Очень забавное утверждение)
Такая ошибка будет видна буквально в первом тестовом запуске на этапе разработки, поэтому ее цена - несколько человеко-минут. Лично я вообще разрабатываю с консолью в соседнем окне, чтобы сразу проверять корректность написанного запроса, поэтому шанс выкатить нерабочий запрос мизерный.
Это ещё даёт типобезопасность. Тип поля в классе всегда будет соответствовать типу данных колонки.
А это еще почему ? Если я запрошу
Select 1::int as field_aА класс будет содержать field_a с типом string, то каким образом ORM обеспечит соответствие типов ? Если каким-то встроенным тайп-кастом, то, на мой взгляд, это не очень хорошо, так как не всегда понятно что к чему приводить(кто является эталоном - БД или приложение).

Kerman Автор
05.12.2025 11:48Такая ошибка будет видна буквально в первом тестовом запуске на этапе разработки, поэтому ее цена - несколько человеко-минут. Лично я вообще разрабатываю с консолью в соседнем окне, чтобы сразу проверять корректность написанного запроса, поэтому шанс выкатить нерабочий запрос мизерный.
Это не так. После переименования поля легко забыть поменять запрос, написанный пару лет назад где-то в редко используемом месте. Это если используется ORM, тогда программа просто не скомпилится, в случае raw SQL это забытое место всплывёт в продакшене и уже сильно позже, когда детали подзабудутся.

YokoLit
05.12.2025 11:48Согласен с тем, что в голом SQL через пару лет может что-то подзабыаться.
Но не совсем понимаю этот момент:
Это если используется ORM, тогда программа просто не скомпилится
Разве ORM при компиляции делает какие-то проверки между моделями и таблицами DB? Это какой-то функционал EF или в других ORM есть похожий механизм(могу судить только по SQLAlchemy - там такого нет)
Или речь про тесты ? В таком случае разницы между rawSQL и ORM быть не должно(все упрется в логику тестов).

Aleksandr_A_A
05.12.2025 11:48В NET есть весьма интересный механизм - деревья выражений (в очень грубом описании, это исходники кода внутри кода). Этот механизм позволяет таким ORM как EF сопоставлять запросы с заранее зарегистрированными моделями таблиц в ORM на уровне компиляции. Ответственность за синхронность структур между моделями ORM и таблицами в DB, по-прежнему, лежит на разработчике. Но есть механизмы сильно упрощающие эту задачу. К примеру, автоматизированная генерация миграций для изменения структуры таблиц в DB (работает на рефлексии) при изменении моделей ORM. Таким образом можно добиться того, работая с таким ORM не нужно писать не единого терма из sql. И почти все твое взаимодействие с DB будет проверяться ещё на этапе компиляции. А также на этапе написания кода за счёт сложностей ide. Кстати, по этой же причине у многих в NET складывается неверное представление, что при использовании ORM не нужно знать SQL. Но это только до первой проблемы с производительностью.
Как может выглядеть задача на переименование колонки с такой orm, если в проекте отсутствуют sql вставки:
Переименовывается поле в моделе ORM. Попутно переименовываются все места использования этого поля в коде за счёт средств рефакторинга ide.
Генерируется миграция для DB через утилиту ORM. Здесь по хорошему нужно перепроверять правильность миграции.
Код компилируется.
При запуске приложения миграция сама изменяет структуру таблиц в DB.
Да, отношение к ORM в NET полностью отличается от других платформ разработки.

YokoLit
05.12.2025 11:48Ого, не знал о такой особенности в NET. Я знал, что там у ребят вообще отдельная кухня, но чтоб там ORM были настолько прокаченными - нет.
Благодарю за ответ.

cyber-jet
05.12.2025 11:48Сущность ORM первоисточник, его отражение это запись в базе. И тут есть варианты в зависимости от структуры. ORM может создавать таблицы под себя с учётом описываемых полей, и/или может отдавать и принимать данные в нужном типе, например когда свойства объектов хранятся в одной таблице и имеют разные типы, классический пример это wordpress.

Lexans
05.12.2025 11:48Ни слова не сказано про основную боль работы с базами данных, это миграции, когда меняется структура базы данных. Единственная orm, которая хороша в этом, это redbeans, так как автоматически меняет структуру бд в соответствии с кодом. А Entity core неплохо реализована авто генерация миграций, в других с этим вообще глухо и непонятно, как разрабатывать софт для декстопа или мобилок, если нет нормальной реализации миграций

Kerman Автор
05.12.2025 11:48Хороший вопрос. Но упоминанием тут не обойтись, это отдельная и глубочайшая кроличья нора.
Я считаю, что задача ORM - работать в рантайме. Весь тулчейн вокруг процесса разработки должен обеспечиваться внешними инструментами. Собственно, я такой инструмент и делаю. Для миграций я сделал отдельный вид генераторов, которым на вход попадает дифф базы данных, а генератор уже под конкретный орм делает миграцию. Это нужно тем, кому необходимо держать историю миграций и создавать ddl откатов.
Это работает так: вы работаете с тестовой и локальной базой и изменяете её без оглядки на миграцию. Когда ваша фича заработала, вы просто сравниваете модель с базой прода и вам генератор выкатывает миграцию. Можно и без миграции накатить изменения на прод, дифф сразу предлагает выбрать изменения, которые нужно накатить.
Ну вот не надо ORM заниматься миграциями.

aladkoi
05.12.2025 11:48Читайте мой, последний пост. Все давно реализовано в крупных корпоративных решениях. Не нужно "изобретать велосипед" .

vitiok78
05.12.2025 11:48В наше время ИИ делает использование ORM ещё менее обоснованным. SQL сильнее многих языков похож на человеческий язык, и модели щёлкают его как семечки. Так же кодогенерацию никто не отменял, и можно найти очень неплохие генераторы, которые напишут вам всё на основе SQL, который вы ему дадите.
И получается, что теперь и скорость разработки не так экономится, а последствия использования ORM всегда очень неприятные, потому что практически в каждом большом проекте наступает момент, когда ORM перестаёт соответствовать вашей специфике, и вы начинаете бороться не с задачей, а с ORM. А этого я никому не посоветую, потому что ублажать этих монстров - та ещё задачка.

aladkoi
05.12.2025 11:48Использование orm требует определенного подхода, это не просто надстройка над sql. В erp odoo трёхслойная надстройка orm над postgresql строит сложную систему моделей данных , связей между ними, наследование и расширение базовых моделей, обеспечивает систему безопасности доступа к ним и к ui пользователя вместе с отображением данных в нем. Так, что здесь SQL некий нижний слой, обеспечивающий orm доступ к данным postrgresql, всю остальную сложную работу по архитектуре бизнес модели системы берет на себя orm. Данную архитектуру современные AI модели знают достаточно хорошо и быстро пишут под нее любую бизнес модель под заданные требования. Роль разработчика сводится не в борьбе с "монстром" в лице orm (который "пилят" 1000 индусов на протяжении 20 лет), а в быстром написании бизнес модели под требования заказчика. Разработчику, не нужно "видеть" как эта "кухня" работает внутри системы, ему достаточного понимать, как быстро "закодить" свою бизнес модель. Вы когда покупаете автомобиль, навряд ли, кроме изучения органов его управления, пытаетесь вмешиваться в работу его двигателя и пытаться там что то переделать. Так и с современными orm системами. Это своего рода готовый автомобиль, которым просто нужно научиться управлять. Что касается разных "не до orm" надстроек, созданных разными любителями типа Lazarus и прочими, здесь, конечно, особого смысла нет их использовать, так как доведение их использования до большой рыночной бизнес модели может потребовать огромных денежных средств и человеческого труда. В современном конкурентном рынке полно уже готовых бизнес orm решений, над которыми крупные компании трудились не один десяток лет.

Artyomcool
05.12.2025 11:48Я просто отмечу, что на моем опыте оверхед от Hibernate - это 20% CPU profile в среднем по больнице. И обычно в этих 20% происходит примерно ничего полезного с точки зрения бизнес-логики.
AndrewBond
Неоднозначное какое-то отношение к ORM. Вместо простого и четкого SQL, который "один на всех", нужно учить объектные модели к каждому фреймворку. При этом, заранее непонятны границы, где закончатся возможности ORM и придется переходить к чистому SQL (уже внутри какого-то метода ORM).
Ну и на практике, два примера, которые среди меня до сих пор основа сомнений, оба про SQL Alchemy.
с разницей в пару лет редактировал одно и то же приложение. Во второй раз обнаружил, что документация почти ни в чем не совпадает с первым заходом. Т.е. за два года поменялось почти всё, знания полностью обесценились. Старый код работал, но в документации не было тех способов, которыми всё было сделано в первый раз.
Нужно было сделать простой листинг записей с разбиением на страницы. В ORM был метод paginate, его и использовал. Всё было норм на тестовых данных, когда же загрузил пару сотен тысяч, внезапно оказалось, что SQL, который генерит ORM запрашивает все данные из таблицы, а потом выбирает нужный диапазон. Могли предупредить хотя бы :(
После этого к подобным оберткам отношусь скептически.
DoctorKrolic
Второй пункт - чистой воды skill issue. Тот же EF пишет в логи весь sql, который он генерирует. И если вы не читали доки фреймворка, с которым работаете, то вам уже ничего не поможет
AndrewBond
Sql я прочитал, когда мне это понадобилось. Но, используя ORM, я не предполагал ,что мне это понадобится.
ValeryIvanov
Так бывает, что библиотекам может понадобиться мажорная миграция. Алхимии она определённо пошла на пользу(ИМХО).
А при необходимости поправить старый код, документация к легаси версии 1.4 всё ещё доступна.
Возможно вы действительно использовали не голую алхимию, а обёртку над ним? Не припоминаю в sqlalchemy метода paginate да и вообще вспомогательного функционала для пагинации. В этом плане он всегда был близок к нативному sql, заставляю использовать offset/limit или самописные курсоры. Гугл выдаёт что метод paginate есть в flask_sqlalchemy, возможно дело в нём.
AndrewBond
да, flask_sqlalchemy. Возможно, из любопытства проверю, как у текущей версии с этим paginate. Как сделать это на чистом SQL знаю, но вопрос "ну как так можно было сделать"?
Tishka17
flask_sqlalchemy это достаточно мутная обертка над алхимией для тех, кто не осилил прокинуть сессию. В ней полтора хэлпера и привязка к flask. Рекомендую использовать алхимию напрямую и иметь поверх неё очень тонкие обертки, сужающие интерфейс до нужного вашей бизнес логике.
Tishka17
Алхимия не менялась тыщу лет. Потом выкатил вторую версию и вот "оно постоянно меняется". При этом синтаксис второй версии намного ближе к тому что происходит в бд.
И нет, sql не один на всех. Попробуйте limit offset в разных СУБД, особенно пятнадцатилетней давности
AndrewBond
Да, для каждого sql есть особенности, но это именно особенности, основа одинаковая. А ORM могут отличаться вообще в корне.
Tishka17
у ORM собственно тоже основы одинаковые. Это либо Active Record, либо Data Mapper. Соотсвтенно, там может быть или отсутствовать Identity Map и UnitOfWork. Я смотрю на условный c# Entity Framework после алхимии и не вижу кардинальных отличий, только особенности связанные с использованием другого языка программирования.
Тут суть скорее в том, что если вы не используете ORM, но хотите иметь сущности бизнес логики, не дай бог с коллекциями, но у вас всё равно получится слой, сильно напоминающий ORM, только менее продуманный и никому не известный (а скорее всего ещё и без документации). Я такое видел, это очень больно, готовые ORM лучше.
Ryav
В документации алхимии можно выбрать версию. И да, прям изменения были при переходе с 1 на 2 версию, при этом в 1.4 пользователей максимально плавно готовили к обновлению, после которого миграция на 2 версию была элементарной (а ещё можно было и не обновляться, выбор каждого)
cyber-jet
Когда начинаешь работать в реале, то понимаешь для чего нужна ORM. Потому что это модель(!) "чистый SQL" эту модель описать не может в силу ограничений в принципе. ORM даёт связи, события, кеширование, вменяемую валидацию, где не обязательно выкидывать исключение, а достаточно преобразования типа как на запись так и на чтение. ORM это уровень абстракции который приходится писать в любом случае если проект отличен от уровня "hello world". Но самое главное, грамотная ORM тебе даст подсветку ошибок и подсказки в редакторе кода.