LINUX.ORG.RU

ORM vs SQL

 ,


0

3

Регулярно читаю на форумах (в том числе и тут подобное встречаю), что ORM юзать не надо, что это оверхед, что Go без ORM отлично живёт, Gorm не нужен - и так про любой язык программирования.

Понятно, что если не использовать ORM, то производительность приложения будет, скорее всего, повыше, и понятно, что ORM обычно накладывает определённые ограничения.

Но приведу пример. У меня есть API-эндпоинт, возвращающий список некоторых сущностей. Там есть вариантов сортировки штук 10, штук 20-30 вариантов опциональной фильтрации и пагинация. Причём для некоторых вариантов фильтрации нужно делать дополнительные JOIN или подзапросы. Я это реализовал через Doctrine DBAL - и получилось довольно удобно и вполне читабельно. Генерируемый SQL-запрос оптимальный и вполне шустро работает.

Но вот чего понять не могу, неужели вы (при разработке без ORM и квери-билдеров) руками конкатенируете строку запроса? Если да, то как вы потом эту адскую лапшу кода разбираете и как контролируете, чтобы запрос не получился невалидным (где-то пробел забыли и т.п.)?

Хотелось бы услышать противников использования ORM и квери билдеров, как вы решаете такие задачи? И на что маппите результаты запросов? На обычные массивы, не на объекты?

Есть разные пути. Самое простое: копипаст. Все варианты запросов тупо повторяешь с нужными сортировками и т.п. Все это оборачиваешь в функции, по одной на запрос. Говнокод конечно, но проще не придумаешь. Другой путь: шаблоны. Что-то типа php, но вместо html у тебя sql с логикой. Результат: опасный говнокод, зато DRY. Наконец, можно как мартыха генерить лапшу собирая запрос по кускам. Но это совсем уже для дегенератов. Мапить результаты очевидно на коллекции структурок, а как еще? Если будешь свои объекты строгать, то это уже будет костыльный недоORM.

anonymous
()

Ах да, забыл путь боярина: всю работу с базой делает специально обученная макака на процедурных диалектах, а у тебя только готовый API, и ты вообще не знаешь, что это за скуль-шмуль такой.

anonymous
()

Но вот чего понять не могу, неужели вы (при разработке без ORM и квери-билдеров) руками конкатенируете строку запроса? Если да, то как вы потом эту адскую лапшу кода разбираете и как контролируете, чтобы запрос не получился невалидным (где-то пробел забыли и т.п.)?

Да, руками грубо говоря.Как контролировать - запрос склеил, вывел принтом/ в логи. Если не работает - видно почему. Потом разбираться - иногда очень тяжело. Возможно немного предвзято - в некоторых случаях склеенный запрос будет красивее и понятнее чем код для орм.

И на что маппите результаты запросов? На обычные массивы, не на объекты?

Как когда. Сейчас в основном кортеж/именованный кортеж, иногда словарь. Маппер - свой велосипед, не универсальный, но текущие запросы покрывает более чем.

Не противник ORM, скажем так - так сложилось, что пока в основном использую только обычные запросы.

Kazun3500
()

ORM юзать не надо, что это оверхед

В том виде, в котором большинство так считающих его понимает, оверхед — миф. Обычно он незначительный.

руками конкатенируете строку запроса?

Ну ты чего, форматирование с помощью соответствующей библиотеки, с эскейпами и всем нужным.

как вы потом эту адскую лапшу кода разбираете и как контролируете

Хочу то же самое спросить тебя про сложные запросы с помощью моделей. Готовый запрос-то целиком лежит в идеале в отдельном файлике и прекрасно читается.

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

WitcherGeralt ★★
()
Последнее исправление: WitcherGeralt (всего исправлений: 2)

как контролируете, чтобы запрос не получился невалидным

тесты пишем. хоть с орм, хоть без.

cdshines ★★★★★
()
Ответ на: комментарий от WitcherGeralt

Что такое «сложные запросы с помощью моделей»? Doctrine DBAL - это просто квери-билдер + маппер на объекты (модели). Ты про Active Record какой-нибудь?

dimuska139 ★★
() автор топика
Последнее исправление: dimuska139 (всего исправлений: 1)
Ответ на: комментарий от anonymous

Все варианты запросов тупо повторяешь с нужными сортировками

Количество вариантов представляешь?))

dimuska139 ★★
() автор топика
Ответ на: комментарий от Kazun3500

в основном кортеж/именованный кортеж, иногда словарь

Так ведь не понятно, какие поля там есть, а каких нет - и нужно каждый раз сам запрос смотреть. Никаких автодополнений, подсказок IDE…

dimuska139 ★★
() автор топика
Ответ на: комментарий от dimuska139

То же самое, что и без моделей.

квери-билдер + маппер на объекты

С маппером или без, не важно.

Даже достаточно простой запрос на строчек 50 (отформатированный), уже будет выглядеть в коде феерически бестолково.

WitcherGeralt ★★
()

Реально смог померять и увидеть существенный оверхед от orm только на базах, где ты размеры табличек без индексов сотнями гигабайт считаешь. Но с такими большими базами, имхо, только «путь боярина», оговоренный выше. Ещё orm сливает если у тебя база совсем уж сильно денормализована и ты не можешь на это повлиять. Я обычно использую orm там, где можно, а если нельзя - хранимочки и prepeared statement-ы.

anonymous
()
Ответ на: комментарий от WitcherGeralt

Тогда я тебя не совсем понял. Вот ты пишешь «Готовый запрос-то целиком лежит в идеале в отдельном файлике и прекрасно читается.». Когда запрос всегда одинаковый - понятно. Но я пример привёл запроса, где куча параметров фильтрации, которые влияют на возможность дополнительных джойнов и подзапросов (этой реальный кейс из проекта, над которым работаю сейчас). Как ни крути, но если это делать без Doctrine/SqlAlchemy и т.п., всё сведётся либо к самописному квери-билдеру (который не факт, что будет лучше готовых решений), либо к конкатенации строк вручную. Разве нет?

В зависимости от проекта голый SQL и ORM у меня могут даже соседствовать.

У меня аналогично. Я пишу на голом SQL в двух случаях:

  • SQL очень сложный и квери-билдерами/ORM его не сделать по-нормальному
  • Когда крайне критична производительность, ORM ощутимо замедляет ответ сервера, а кешировать невозможно.

Если интересно, могу высказаться по поводу того, что и когда хорошо, а когда плохо.

Интересно, конечно

dimuska139 ★★
() автор топика
Ответ на: комментарий от anonymous

увидеть существенный оверхед от orm только на базах, где ты размеры табличек без индексов сотнями гигабайт считаешь

А как использование ORM связано с размером таблиц? Если нет бредовых запросов типа выборок связей через отдельные запросы, то разницы ж никакой.

dimuska139 ★★
() автор топика
Ответ на: комментарий от dimuska139

Если брать db/2 или ms sql server, то у тебя план выполнения в хранимом объекте СУБД уже рассчитан ещё до выполнения самого запроса и более-менее предсказуем, а вот на динамических запросах оно полность на ходу генерируется оптимизатором. Оно, особенно под нагрузкой, очень неоптимальные планы строит иногда, причём непредскозуемо. Вот на маленькой базе ты эти огрехи просто совсем незначительны.

И почему ты использование подзапросов для получения связей бредом считаешь? Разве никогда не было такого, что структура бд оптимизировалась под одни запросы, а потом через насколько лет существования программного комплекса резко понадобилось добавить такое, что по другому и не сделаешь без перекраивания структуры полностью?

anonymous
()

неужели вы (при разработке без ORM и квери-билдеров) руками конкатенируете строку запроса?

Во-первых ORM и билдеры ортогональны. Во-вторых неужели ты не в курсе про prepared statements?

Понятно, что если не использовать ORM, то производительность приложения будет, скорее всего

Вообще-то обычно наоборот. Если всё делать правильно. ORM позволяет прикрутить кэш объектов и т.о. избавиться от повторения запросов. Т.е. один запрос без ORM вероятно можно сделать оптимальнее и быстрее, но кучу похожих запросов в многопользовательском приложении (читай, сайт или api) ORM прожуёт гораздо шустрее.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 2)
Ответ на: комментарий от dimuska139

Если нет бредовых запросов типа выборок связей через отдельные запросы

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

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от dimuska139

Но я пример привёл запроса, где куча параметров фильтрации, которые влияют на возможность дополнительных джойнов и подзапросов

Дело в том, что запросы бывают разные. Есть банальный CRUD, где ORM рулит и педалит т.к. и пишется быстро за счёт скаффолдинга и работает быстро за счёт кэша.

А есть аналитика, где нужно хитро заджойнить кучу таблиц, отфильтровать, сгруппировать, сделать агрегацию и т.д. Тут ORM просто не нужен т.к. результат такого запроса вообще представляет собой не какую-то сущность-объект, а некий отчёт-датасет.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от anonymous

И почему ты использование подзапросов для получения связей бредом считаешь?

Не подзапросы в рамках одного SQL-запроса, а отдельные запросы. Неправильная настроенная ORM вместо LEFT JOIN может сделать выборку из одной таблицы SELECT * FROM users LIMIT 1000, а после этого циклом сделать кучу запросов на выборку из другой вида SELECT * FROM books WHERE user_id=1..1000. И после этого она маппит их на объекты.

Поэтому чтобы не париться насчёт таких неочевидных моментов я использую квери-билдеры + мапперы, а не ORM целиком.

dimuska139 ★★
() автор топика
Ответ на: комментарий от dimuska139

Неправильная настроенная ORM

Это правильно настроенная ORM. Ещё более правильная сделает отдельный SELECT … IN чтобы загрузить все сущности в один заход, но точно не LEFT JOIN.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Во-вторых неужели ты не в курсе про prepared statements?

В курсе, но не очень понимаю, как это избавит от необходимости по кускам в if-аках конкатенировать строку запроса.

Если всё делать правильно. ORM позволяет прикрутить кэш объектов и т.о. избавиться от повторения запросов

А есть, кстати, ощутимые преимущества такого подхода? Ведь можно просто кешировать ответы API и всё.

Это совсем не бредово, т.к. каждый такой запрос можно кэшировать отдельно и уже не повторять.

Разве обычный JOIN не будет работать быстрее, чем куча запросов циклом (даже если часть из них будет доставаться из кеша)?

dimuska139 ★★
() автор топика
Ответ на: комментарий от no-such-file

Реально не понимаю, в чём преимущество. Та же Doctrine ведь без проблем делает LEFT JOIN и точно так же без проблем, насколько я знаю, может закешировать такой результат. И будет всего 1 запрос в базу, а не гора.

Ещё более правильная сделает отдельный SELECT … IN

Ну, в той же Django есть и select_related (LEFT JOIN) и prefetch_related (SELECT … IN). При этом если связь 1-1, то select_related работает значительно быстрее. По крайней мере, в тех ситуациях, с которыми я сталкивался. А вот когда 1-n, в Django prefetch_related сделает SELECT … IN.

dimuska139 ★★
() автор топика
Последнее исправление: dimuska139 (всего исправлений: 1)
Ответ на: комментарий от dimuska139

Я с тобой согласен, дополню только.

Когда предпочтителен голый SQL

Запрос даже не обязательно должен быть сложным. Это может быть не особенно параметризованная агрегация данных, результат которой ты не будешь мапить на объект, либо ты получаешь просто какое-нибудь единичное значение, например максимальную версию данных, или что-нибудь ещё в этом роде. Построение такого запроса на месте не будет нести никакой смысловой нагрузки, лишь создаст визуальный шум, в функцию это выносить смысла тоже скорее всего нет, лучше просто написать нормальный SQL и убрать в отдельный файлик. В коде вместо простыни запроса это будет выглядеть как-то наподобие этого:

result = db.query('avg_some_shit', {'date': date})

Когда ни в коем случае не нужно писать голый SQL (при наличии ORM в проекте)

  • Bulk insert.
  • Апдейты — ибо лучше наглядно показать, что будет обновляться прямо в коде, а не где-то ещё.
  • Запросы для выборок.
WitcherGeralt ★★
()
Последнее исправление: WitcherGeralt (всего исправлений: 2)
Ответ на: комментарий от dimuska139

насколько я знаю, может закешировать такой результат

Кэшировать результат, или ответ API целиком не очень полезно, т.к. любое изменение данных потребует полной перезагрузки. ORM позволяет перезагрузить только одну изменённую сущность, причём тривиальным запросом, который БД прожуёт гораздо быстрее.

Пример. У тебя сайт, на странице 10 постов. Ты делаешь запрос SELECT … JOIN чтобы получить посты и связанные данные (пользователи и т.п.) и всё это закэшировал. Допустим один пользователь поменял ник. Тебе придётся перезагрузить весь датасет тем же SELECT JOIN. ORM перезагрузит только объект пользователя простым SELECT ID =. Ну и что быстрее?

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 2)

Строю запрос руками. Проблем нет. Если забуду пробел, всплывёт при тестировании. Маплю на объекты. Про массивы не понял, как можно маппить на массивы.

В принципе против ORM ничего не имею, но он мне кажется не нужным, тк не решает проблем, которые мне было бы сложно решить. А написать SQL и замапить результаты мне никогда не казалось сложным.

Кстати подозреваю, что твоя пагинация работает неоптимальной. 99 из 100 пагинаций сделаны неправильно. Это большая проблема.

Legioner ★★★★★
()
Последнее исправление: Legioner (всего исправлений: 1)
Ответ на: комментарий от dimuska139

Количество вариантов представляешь?))

Да какбе насрать. У тебя ресурс клавиатуры ограничен или размер сорцов? К тому же не так уж и много вариантов на самом деле. Явно не миллионы. А часто бывает, что запросов совсем мало, но мвртыхи все равно изобретают какую-то херню на ровном месте.

anonymous
()

ORM и квери билдеров

А зачем ты их кстати в одну, кучу свалил? Это совсем разные паттерны. «Квери билдер» это тупо трансляция в sql, там та же самая конкатенация под капотом. Это к вопросу о том, на кой хрен вручную это делать. ОРМ это декларативная байда, которая никогда нормально не работает. Там абстракции текут как снежные бабы по весне.

anonymous
()
Ответ на: комментарий от anonymous

Потому что его волнует именно вопрос ручного написания SQL.

WitcherGeralt ★★
()
Ответ на: комментарий от Legioner

99 из 100 пагинаций сделаны неправильно. Это большая проблема

Если ты про проблему с большим OFFSET, то там где используется слово «пагинация» это не является большой проблемой, т.к. там нет сколько данных (или они неактуальны).

no-such-file ★★★★★
()

Если да, то как вы потом эту адскую лапшу кода разбираете и как контролируете, чтобы запрос не получился невалидным (где-то пробел забыли и т.п.)?

Да никак. Упало приложение, «глянь, чего там не так». Берешь qDebug и вперед.

LongLiveUbuntu ★★★★★
()

Господа, вы тут за кеширование заговорили. Причём кеширование к orm? Вот что мишает прикрутить кеширование к тому же mybatis, который ни разу не orm, а просто маппер результатов SQL-запросов? Несколько строчек в конфиге.

anonymous
()

Использую hibernate/jpa в java. Вообще не понимаю, как эта тема может быть интересна?
orm следует использовать до тех пор, пока его возможно использовать. period.
Любой «текст» не являющийся кодом на языке проекта (включая хранимки) ведёт к стрессу.

system-root ★★★★★
()
Последнее исправление: system-root (всего исправлений: 1)
Ответ на: комментарий от WitcherGeralt

Не, это ты не понимаешь, как соотносятся понятия уверенности в коде и тестирования, поэтому и жаль. А советы раздаёшь.

cdshines ★★★★★
()
Ответ на: комментарий от Legioner

Про массивы, наверное, имеет в виду джойны, которые для одного ключа производят много строк, но это решается, например, группировкой и агрегацией в json, если не слишком критично по производительности (и то надо тестить), а можно и на клиенте собрать, как ормы и делают. В целом, считаю, что писать сырой sql - годный подход, нужно просто не вклеивать напрямую пользовательский ввод и думать головой.

cdshines ★★★★★
()
Ответ на: комментарий от anonymous

У меня ограничено время. Написать отдельные варианты запросов под каждую группу возможных условий фильтрации - это много времени уйдёт. Их количество может дойти до сотни без особых проблем.

dimuska139 ★★
() автор топика
Ответ на: комментарий от Legioner

Если ты про OFFSET, то в моем проекте это пока что совершенно не критично. В той таблице около 500к строк и не факт что даже до миллиона будет доходить. Если будут проблемы, то курсорами буду изощряться.

dimuska139 ★★
() автор топика
Ответ на: комментарий от WitcherGeralt

В Typeorm для postresql в m2m с составным первичным ключом приходится bulk insert делать руками из-за бага, фикс которого пока не прилили)) если вдруг столкнёшься, имей в виду

dimuska139 ★★
() автор топика

Регулярно читаю на форумах (в том числе и тут подобное встречаю), что ORM юзать не надо, что это оверхед, что Go без ORM отлично живёт, Gorm не нужен - и так про любой язык программирования.

Нашёл, кому верить.

Понятно, что если не использовать ORM, то производительность приложения будет, скорее всего, повыше, и понятно, что ORM обычно накладывает определённые ограничения.

Нет, не понятно. Производительность в данном контексте зависит от двух моментов:

  • умеет ли ОРМ выбрать все нужные данные за один-два запроса, вместо того, что бы выбирать по одной строке, посылая в БД 9000 запросов
  • понимает ли разработчик с ОРМ, что именно он делает(т. е. насколько хорошо он знает используемую ОРМ)
  • умеет ли разработчик, не использующий орм, то же самое

Но вот чего понять не могу, неужели вы (при разработке без ORM и квери-билдеров) руками конкатенируете строку запроса?

Да.

Если да, то как вы потом эту адскую лапшу кода разбираете и как контролируете, чтобы запрос не получился невалидным (где-то пробел забыли и т.п.)?

Разбираю так же, как и остальной код. Если это был заход на тему «читаемости» - это бред. Нет никаких проблем прочитать код функции на 30-100 строк, читаемость не про это. Читаемость - это про схемы взаимодействия компонентов(и понимание причин, почему именно так сделано, а не иначе). Невалидный запрос, опять же, ищется так же, как и остальные мелкие ошибки в коде. Про тесты уже сказали.

anonymous
()

Давайте сначала определимся с терминологией

  • ORM - описывает связи (в понимании реляционной бд) между объектами

  • Result Mapper - переводит результат запроса в какое-то програмное представление (объекты, структуры, т.п.)

  • Query Builder - програмный составитель запросов

  • Migrator - выполняет миграции версий схемы бд

В Go это все ортогональные вещи

ORM. Плюсы: програмно описываются связи между сущностями, мне это помогало для N+1 запросах. Минусы: програмно описывать эти связи может быть утомительно и не решать никаких практических проблем.

Result Mapper. Плюсы: в целом всегда полезная штука, не вижу причин не использовать их. Минусы: они могут не справляться с такими фичами, как курсоры, когда данные приходят батчами или итерациями.

Query Builder. Плюсы: защищает от ошибок при контатенации запросов строками. Удобно составлять динамические параметры. Минусы: если запрос не изменяется, иногда проще его все-таки как строку хранить. Обычно кверибилдеры становятся раком когда нужны вложенные подзапросы, цте и специфические бд вещи. Еще многие не умеют в prepared statements

Migrator из определений ORM. Плюсы: исходя из имзенений моделей, миграор сам находит изменения и генерирует изменения к базе. Минусы: блин, реально обычно они полную чепуху делают

Migrator из скриптов. Плюсы: видно всю схему бд. Минусы: нужно отслеживать самому изменения в схеме и менять определения моделей в коде самому. Хотя я предпочитаю этот вариант.

Немного могу сказать о gorm, о библиотеке которую время от времени использовал в течении трех или почти четырех лет. Как квербилдер мне нравится больше всех, хотя как сказано выше, если нужно цте или подзапросы это делается очень стремными костылями. Никогда не использовал миграции гормовые, только скрипты ручками писали. Из удобных фич горма - это логгер запросов, адекватный маппер результатов и мне много раз пригождались гормовые прелоады для N+1 запросов когда нельзя все данные вытащить джоинами. В общем, как-то так.

nikolnik ★★★
()
Ответ на: комментарий от WitcherGeralt

Поболее твоего, видимо, понимаю, так как ты даже в паре предложений на русском языке запутался.

А где, покажи)

К зеркальцу подойди.

Вроде все логично. Делаем вывод, что твоему профессиональному мнению, как программиста, можно спокойно доверять. wait, oh shi~

cdshines ★★★★★
()
Ответ на: комментарий от WitcherGeralt

Ну да, было бы неплохо узнать, почему «тестирование» как ответ на вопрос «как вы убеждаетесь, что ваши запросы написаны корректно» - это высер.

cdshines ★★★★★
()
Последнее исправление: cdshines (всего исправлений: 1)
Ответ на: комментарий от nikolnik

Давайте сначала определимся с терминологией

Молодец, хорошо расписал как всё это говно больше мешает жить, чем помогает.

bread
()
Ответ на: комментарий от cdshines

Запросы можно просто валидировать вообще без бд, можно тестировать каждый по-отдельности, можно функционально, а ещё у тебя как бы БД, тут уже и интеграционными тестами попахивает. Как именно ты тестируешь? Загонятьешь в бд сгенерированный набор данных, копию живых данных? Можно выбирать данные, можно ничего не выбирать, а просто удостовериться, что запрос корректный. Можно писать стратегию. БД можно эталонную иметь для тестов, а можно запускать и набивать в контейнере на раз. Не совсем по теме топика, но кривой запрос может быть и валидным, но на производительность фатально повлиять, так что и регрессию производительности можно отлавливать.

Что тебе не ясно было в словосочетании «слишком общие слова»? Тебя спросили «как?», а ты ответил «пиздато!».

WitcherGeralt ★★
()
Последнее исправление: WitcherGeralt (всего исправлений: 4)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.