Пользователь
0,0
рейтинг
31 января 2014 в 06:29

Разработка → Хранимые функции. За и против

Использование хранимых функций СУБД для реализации бизнес-логики или её части, всегда было камнем преткновения. С одной стороны баррикад DBA и программисты БД, с другой — разработчики backend.
Рискну навлечь на себя гнев из обоих лагерей, но всё же просуммирую плюсы и минусы и изложу свои соображения о том, когда стоит писать код в хранимых функциях, а когда следует выносить наружу.




Начнём с аргументов против:

Размазывание бизнес-логики


Это, на самом деле не проблема СУБД и ХФ, как инструмента — это проблема их неверного использования. У программиста бд может возникнуть желание описать всю логику реализуемого действия в хранимой функции — действительно, ведь все данные вот они, под рукой. Если программист поддастся на искушение, а его руководитель не возразит, в будущем могут возникнуть проблемы с узостью интерфейса со внешней системой (например, с сервером приложений) — придётся добавлять новые параметры, усложнять логику и т.п. Это даже может привести к тому, что появятся «дублирующие» ХФ со слегка иным функционалом.

Скудность языка СУБД


Есть такое дело. Традиционные языки для написания ХФ pl/sql, t-sql, pl/pgsql довольно примитивны по сравнению с современными языками общего назначения. Стоит заметить, что есть возможность писать ХФ и на более продвинутых языках, например Java в Oracle или Python в postgresql.

Непереносимость хранимых функций


Имеется в виду несовместимость диалектов процедурных языков разных СУБД. Многоплатформенность как раз на уровне — благодаря поддержке разных ОС и архитектур в самих СУБД и независимости встроенных языков от внешней платформы. Здесь опять решение зависит от специфики проекта. Если проект тиражируемый, причём вы не контролируете платформу (классический пример — CMS), то переносимость вам необходима и использование ХФ — только добавит головной боли. Если же проект уникальный, либо внедрения будут происходить унифицировано (например в разных филиалах одной компании), то про непереносимость между разными СУБД можно забыть.

Отсутствие необходимых навыков у команды и высокая «стоимость» соответствующих специалистов


Это, на мой взгляд, самый серьёзный аргумент против использования ХФ. Тут всё зависит от масштабов проекта. Грубо говоря, использование хранимого кода на стороне СУБД оправдано в средних-крупных enterprise проектах. Если проект помельче — овчинка выделки не стоит. Если проект огромный сверхнагруженный, то архитектура с ХФ и РСУБД упрётся в проблемы масштабирования — тут необходимо использование специфического хранилища и подхода к обработке данных.

Теперь плюсы:

Скорость


При обработке даже небольших объёмов данных во внешнем приложении мы тратим дополнительное время на передачу по сети и преобразование данных в нужный нам формат. К тому же в СУБД уже встроены, отлажены и протестированы близкие к оптимальным алгоритмы обработки данных, вашим программистам незачем практиковаться в изобретении велосипедов.

Сокрытие структуры данных


С ростом и эволюцией программной системы схема данных может и должна меняться. Хорошо спроектированный программный интерфейс на ХФ позволит менять схему данных не изменяя код внешних приложений (которых может быть несколько). Отсюда органично вытекает и разделение ролей разработчиков, которые работают с БД и знают её структуру, и разработчиков внешних приложений, которые должны знать лишь предоставляемый API. При использовании динамического SQL на стороне приложения, для подобного разделения вводятся дополнительные слои программных абстракций БД, различные ORM.

Гибкое управление правами доступа


Хорошей практикой является ограничение пользователя, под которым «ходит» в базу клиентское приложение в правах таким образом, что он не имеет прав на чтение и изменение никаких объектов. Лишь выполняет разрешённые ему функции. Таким образом можно жёстко контролировать какие действия доступны клиенту, уменьшается вероятность нарушения целостности данных из-за ошибки клиентского приложения.

Меньшая вероятность SQL injection


При использовании динамического SQL со стороны клиентской программы, клиентская программа передаёт СУБД SQL команды в виде строк, предварительно формируемых в коде. При формировании этих строк программисту нужно быть предельно внимательным, чтобы не допустить возможности непредусмотренной модификации SQL команды. При использовании ХФ SQL код на стороне приложения обычно статический, и выглядит, как простой вызов ХФ, параметры которой передаются не строками, а через placeholders (:variable) через механизм binding. Конечно это не исключает возможность SQL injection полностью (ведь можно умудриться в ХФ конкатенировать строку, переданную параметром с текстом динамически выполняемого SQL запроса), но значительно уменьшает её вероятность.

Повторное использование SQL


Реализуя логику работы с данными в хранимом слое мы получаем привычную нам иерархическую модель повторного использования SQL кода.
При использовании динамического SQL повторное использование запросов затруднено.
Например пусть есть система A на базе ХФ и система Б на базе динамического SQL. В обеих системах есть функция получения цены товара get_price. В случае A — это хранимая функция или отображение (view), в случае Б, допустим, процедура на java, через JDBC выполняющая SQL запрос. Есть задача — получить общую стоимость товара на складе. В случае A мы джоиним get_price прямо в запрос, получающий список товаров на складе (в случае, если get_price — view или ХФ на SQL, как например в PostgreSQL, то оптимизатор разворачивает запрос inline — тем самым получается один запрос, который быстро находит сумму).
В случае B есть два варианта — либо пробежать по курсору с выборкой товаров на складе и n раз вызвать get_price (а это значит что вся выборка должна передаться по сети на клиент) либо забыть про повторное использование и написать подзапрос, дублирующий тот, что был уже написан в get_price. Оба варианта — плохие.

Простая отладка SQL


Упрощается отладка (по сравнению с разнородной процедурой внешний код+sql)
В системах с динамическим SQL (любые ORM) даже простая задача поиска проблемного куска SQL может оказаться сложной.
Семантическая и синтаксическая проверка SQL на этапе компиляции.
Возможность профилирования функций и поиска узких мест.
Возможность трассировки уже запущеной и работающей системы.
Автоматический контроль зависимостей — при изменении определения объекта инвалидируются зависимые сущности.

Когда писать бизнес-логику в БД?


Если важна скорость обработки данных

Обработка данных прямо на месте их хранения зачастую даёт значительный прирост скорости обработки. Становятся возможными такие оптимизации, как, например, агрегации на уровне хранилища данных — данные с массива даже не передаются на сервер СУБД, не говоря о клиенте.

Когда важна целостность и непротиворечивость данных

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

Данные имеют сложную, но устоявшуюся структуру

Плоские и слабо взаимосвязанные структуры часто не требуют всего богатства инструментов обработки, которые предлагают СУБД. Для них можно использовать сверхбыстрые key-value хранилища и кеширование в памяти.
Сложно организованные сильно связанные иерархические и сетевые структуры — явный показатель, что ваши знания РСУБД пригодятся!

Когда выносить код наружу?


Работа с внешними данными

Если специфика системы такова, что данных, приходящих на обработку снаружи (с датчиков, из других систем) больше, чем данных, сохраняемых в БД, то многие плюсы БД, как платформы программирования теряются. Оказывается проще обработать поступающие даннные снаружи и сохранить результат в БД, чем сначала всё пихать в БД, а потом обрабатывать. Здесь соблюдается тот же принцип — обрабатывать данные как можно ближе к источнику, о котором мы говорили выше применительно обработке данных, уже хранящихся в БД.

Сложные алгоритмы

Сложные или высоко-оптимизированные алгоритмы-числодробилки лучше писать на более приспособленных для этого языках. Встроенные языки РСУБД очень мощны (в том смысле, что высокоуровневые, а не гибкие), но за счёт этого имеют высокий overhead.

Highload

В сверхвысоконагруженных системах обычные подходы к сериализации транзакций и синхронизации серверов кластера становятся узким местом. Для таких систем характерны уникальные решения под конкретные задачи, универсальные и мощные системы РСУБД часто оказываются слишком медлительными при нагрузках в сотни тысяч конкурентных транзакций в секунду.

Вывод такой, что чёткого алгоритма нет. Каждый раз решение остаётся за архитекторами и менеджером и от него зависит то, завязнет ли проект в проблемах с race conditions и неконсистентностью данных NoSQL, проблемах с производительностью и отладкой запросов ORM, или упрётся в проблемы масштабирования СУБД при использовании хранимых функций. Поэтому — принимайте верные решения :)
Сергей Мелехин @CPro
карма
24,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (191)

  • +5
    Пост, конечно, холиварный. Начинаю!

    Скудность языка СУБД

    Вот оценки количества строк, необходимых на реализацию одной не выровненной функциональной точки, для тройки популярных языков (Avg Median Low High).

    J2EE 46 49 15 67
    C# 54 59 29 70
    PL/SQL 37 35 13 60

    PL/SQL — лучший в этой популярной тройке.
    • +20
      Ну тут можно возразить, что компактность кода это конечно хорошо, но есть ещё много факторов, влияющих на оценку языка программистом. Декораторы, генераторы и прочие «плюшки» в языках общего назначения появляются раньше.
    • +7
      Ага, холиварный. По заголовку сюда идут сплошь датабазники, которые нежно любят хранимые процедуры :).

      Хотя это всё — высокие материи. Прикладные программисты, которые умеют писать нормальный SQL и знают, что индексы нужны — уже хорошо. Не стоит требовать от людей слишком многого :).
      • +7
        Я по сфере своей сугубо бэкэндщик, но ХФ просто обожаю. На первой работе меня научили грамотно работать с SQL, после этого и запросы, создынные через ORMы кажутся жалким подобием Juniorского мозга.
        • +1
          Ну такие бэкэндщики, как Вы — вообще клад. Беречь как зеницу ока, при пожаре выносить в первую очередь. Просто вас таких мало, и не стоит от всех ожидать подобного уровня :).

          У меня пользователи время от времени своими запросами сервер ставят на колени. Как в анекдоте: «Ну и запросы у вас!» — сказала БД и повисла. Их не научили писать нормальный SQL, их не научили работать с ХП/ХФ — но они не дураки и вполне способны научиться. И это, как я говорю, уже хорошо. Если человека не гнобить, а учить — его осенит сначала, что индексы нужны. А потом он и до ХФ помаленьку дойдёт :).
          • +7
            Нет, следующий шаг — осознание, что индексы нужны не всегда)
            • 0
              Я же не говорю, что после осознания нужности индексов человек СРАЗУ поймёт всё про ХП :). Так что там «потом» совсем не в смысле «следующий шаг» :).

              Кстати, с моей точки зрения, индексы нужны всегда. Но не все :).

              С другой стороны, вопрос этот я склонен считать религиозным. Если Вы сообщите, что в случае маленькой таблицы, в которой данные обновляются очень часто, выгоднее не заморачиваться с индексами, которые тоже нужно будет обновлять, а поиск всё равно выгоднее будет вести полным сканированием таблицы в памяти — то я спорить не буду. Случаи эти, однако, очень редкие и очень специальные. Ну пусть будет «индексы нужны почти всегда» :).
              • +2
                Индексы могут отрицательно сказаться на производительности и в случае больших таблиц в независимости от вставок. Например по неуникальным значениям, с мылым количеством различных значений. Дешевле фулскан обойдется.
                • 0
                  Что характерно, Вы опять правы. И я только что понял, в каком смысле было сказано ваше «нужны не всегда». Вы говорите о запросах — да, бывают такие запросы, при которых лучше без индекса вообще. То есть в вашем смысле индексы в самом деле нужны не всегда и даже не почти всегда.

                  Я по специфике работы говорил о таблицах. Наши программисты создают табличку через select into, а вот повесить на неё хоть какой-то индекс — это уже продвинутый скилл. А потом они делают select c соединением пары таблиц, ни на одной из которых индекса нет вообще — и оптимизатор гонит мучительный hash join, ставя на колени весь сервер. А потом я им мягко рассказываю, где они неправы :).

                  Моё «почти всегда» значило «почти у всех таблиц индекс должен быть, а если его нет — вы должны понимать, почему то, что его нет, правильно». То есть в моём смысле — почти всегда нужны. Думаю, что Вы согласитесь :).
                  • 0
                    «почти у всех таблиц индекс должен быть, а если его нет — вы должны понимать, почему то, что его нет, правильно»

                    Встречался с таким подходом. Зачастую он выглядит так: таблица с парой десятков полей и каждое поле проиндексировано. По-моему, типичный случай преждевременной оптимизации.
                    • 0
                      это как говорил Optik ниже
                      Люди узнают про индексы и суют куда не попадя.
                    • +1
                      Закройте базу 1С.
                      • 0
                        И без 1С такого добра хватает.
                • +2
                  нужны / не нужны, в нашем деле главное это план запросоа
                  • 0
                    Ну дык без плана вообще не имеет смысла говорить о нужен/не нужен.
                    Но есть один нюанс наблюдаемый в реальности. Люди узнают про индексы и суют куда не попадя. Потом часть узнает, что от этого иногда бывает плохо. И т.д. и т.п. Идут по вполне очевидным граблям. Но вот про план, даже если и знают, хрен посмотрят (в большинстве своем).
                • 0
                  в независимости от вставок
                  И
                  фулскан

                  Полагаю, вы подразумеваете запросы, а не DML. Если так, то с малой кардинальностью (мощностью множества значений) конкретного столбца bitmap-индексы, созданные для него, могут быть — например, когда вся таблица не умещается в оперативную память — эффективней full scan'ов, потому как сканировать нужно не все блоки («страницы» в терминах Microsoft SQL Server) всей таблицы, а лишь bitmap'ы, в которых отражены адреса блоков (страниц) с искомыми значениями.
                  • 0
                    С битмап индексами не все однозначно и просто. Они хороши для множества полей, по одному-двум полям профита не будет. Полное сканирование таблицы тоже не всегда ужасно. Особенно если есть возможность использовать параллельные чтения.
    • +1
      Максимальное различие чуть больше 50%. Даже не в 2 раза. Мне кажется, пример неубедителен.
      J2EE 46 49 15 67
      C# 54 59 29 70
      PL/SQL 37 35 13 60
      MAX DIFF 31,5% 40,7% 55,2% 14,3%

      • +3
        Пример должен был убедить не в том, что pl/sql лучше, а в том, что он не настолько уж хуже, как об этом в статье написано.
        • 0
          Именно так.
    • –2
      Pl/Sql — неплох. Самая жесть когда на MSSQL на T-SQL-хранимках проект делают. Вот это — запредельное зло.
      • 0
        почему?
        • 0
          Хотя бы потому что в T-SQL один оператор цикла while, и чтобы пройтись курсором по таблице — надо строк 10 написать. Код, который на C# занимает 10 строк, на TSQL занимает три страницы, требует несколько дней на написание, и исполнен хаков и боли.
          • 0
            не совсем ясно какую вы задачу решаете, возможно это можно решить просто на SQL. ну а вообще в t-sql есть курсоры с которыми можно работать почти так же как и pl/sql.
            • +1
              Да, пройтись курсором по таблице — 10 строк кода.

              Я решал разные задачи на этом TSQL в сумме лет пять. Мне есть с чем сравнивать, и я всем рекомендую бежать от TSQL. Либо в сторону того же postgres если уж хочется хранимок. Либо, если уж MSSQL, то в сторону какого-нибудь Entity Framework.
              • +1
                может там и будет 10 строк кода пример, но на другом ЯП мне кажется будет не меньше. я только имел ввиду, что циклы используются редко. возможно ваша задача решилась бы простым запросом на SQL, об этом так же говорит foxkeys
                • –2
                  Да хрен с ними с курсорами. Если бы только они. Там же все гнилое. Вот вспомнишь без stack overflow как исправить select sum(timeTaken) from Tasks, тип timeTaken — time?
                  • +6
                    А здесь-то Вам чем T-SQL не угодил? :)

                    Проблема здесь, кстати, в том, что тип данных time придуман для того, чтобы отмечать момент времени, а не продолжительность. Поэтому исправление должно быть связано с изменением способа хранения данных.
                    • –3
                      Момент времени — это продолжительность периода от начала суток до текущего момента. TimeSpan во всех языках успешно применяется и для времени суток, и для продолжительности. То, что такого нет в TSQL, и что при наличии кучи типов про время и дату приходится хранить секунды в int-ах — проблема TSQL.
          • +6
            Наличие while в TSQL коде в большинстве случаев означает плохое знание самого TSQL.
            99% операций могут и должны делаться стандартными операторами SELECT/UPDATE
            С ходу — только один безусловный случай когда применение курсоров оправдано — вызов других процедур для каких-то строк выборки. Причем, зачастую, и их можно заменить «неправильными» функциями, меняющими данные (их вызов будет осуществлен при выборке самим TSQL). В других случаях — для построчной обработки выгодно использовать триггеры.
            Одним словом, случаев безусловной надобности курсоров в реальных проектах очень мало.
            • +4
              Применение курсоров оправдано очень много где. Если мы уже говорим о бизнес-логике в хранимках — то там точно будут и while-ы, и курсоры, и триггеры, и заборы, и коровники.
            • +4
              Я в разговоре о курсорах становлюсь неадекватным.

              Это про них, про них Джефф Моден (Jeff Moden) придуман акроним RBAR — Row-By-Agonizing-Row. Ну и ещё про некоторые такие же запросы, когда нужно для каждой строки какое-нибудь нетривиальное действие сделать.

              Блин, я своими глазами видел вычисление среднего значения через курсор. Да-да, в цикле мы просуммируем и записи посчитаем. А потом одно на другое разделим. А потом будем тормозной MS SQL ругать.
            • 0
              А как же FOR SELECT…
              Половина наверное ХП такую конструкцию содержит.
          • +2
            Как правило, люди, использующие курсоры в T-SQL не понимают, чем SQL (Structured Query Language) отличается от PL (Programming Language) и применяют их в случаях, когда применять их категорически вредно.
            • 0
              Procedural Language
              • 0
                Я не про PL/SQL говорю, я про языки программирования в целом.
          • +2
            Слушайте, а зачем вам проходиться курсором по таблице?

            И можно конкретный пример задачи в 10 строк на C#, которая на TSQL делается несколько дней?
            • +1
              Вот, например, боевой код: pastebin.com/H4nht6aZ Сортировка, фильтрация, пейджинг (путь и довольно хитрый). 560 строк.

              Не помню сколько это заняло, не меньше чем день. Тоже самое на C# с LINQ пишется за час. Единственная проблема — нужно два раза в базу сходить и чуть побольше чем надо оттуда вынуть.
              • 0
                А можно полную формулировку задачи?
                И какой смысл в строках «left outer join propertyInformation b (nolock)» на 72, 193 и 200 строках?
                • 0
                  Боюсь я уже точно все не вспомню, дело было давно.

                  Смысл left outer join — прицепить перевод, если есть для данного языка. Вероятно там можно было это сразу стащить во временную таблицу, если ты про это. Nolock — базист-фашист наверное понатыкал, впрочем база считай readonly была, может смысл и был.
        • 0
          присоединяюсь. сам их не люблю, но не знаю почему)
      • +3
        Напрасно вы так. При грамотном применении — TSQL очень хороший инструмент.
        • +5
          Может быть для чего-то он и очень хороший инструмент. Например его можно показывать разработчикам языков — чтобы так больше не делали, или пытать людей, заставляя на нем писать.

          TSQL — это на данный момент, с большим отрывом от всех живых конкурентов, самый плохой встроенный в БД язык. Уж на что PL/SQL старый и ретроградский, но он хотя-бы язык. Да какой PL/SQL, я на Interbase 15 лет назад юзал фичи, о которых TSQL-разработчики сейчас и не мечтают даже. Например yield-ил строки из одной хранимки, а в другой из нее делал select.

          И да, в interbase/firebird минимум 15 лет уже есть тот же foreach по select-у! Без всяких open cursor, реально — берешь и делаешь цикл по строкам! Вау!
          • 0
            то что в PL/pgSQL или PL/SQL можно делать FOR rec IN SELECT… да удобней, что не нужно объявлять курсор, но то всего одна строчка да и не так уж напрягает
            • 0
              Если таблица Tasks, в ней TimeTaken с типом Time. Нужно сделать select sum(TimeTaken) from Tasks. Сможешь без stackoverflow?

              В TSQL вот такое гнилое — абсолютно все, куча всего что тривиально во всех других современных языках, в TSQL делается какими-то хаками.
              • 0
                Можно просуммировать datepart'ы нужной точности (например, в секундах), но есть, конечно, нюанс с возможным выходом за пределы Int'а.
              • +1
                Зачем вам нужно суммировать время дня? Какой в этом физический смысл?
                time (Transact-SQL) — Определяет время дня. Время без учета часового пояса в 24-часовом формате.
                • 0
                  Ок. В чем в MSSQL предлагается хранить промежуток времени? К слову, физический смысл понятия «время дня» — это промежуток времени между началом суток и текущим моментом. Во большинстве языков и для времени суток, и для промежутка времени используется один и тот же тип. Например в .net это TimeSpan.
                  • 0
                    В тиках, например (bigint). Или в секундах (bigint или decimal или float, в зависимости от того какая точность нужна). Они, в отличие от календарных типов, прекрасно складываются, вычитаются и легко конвертируются во что угодно.
              • 0
                Вот вы прикопались-то к этой сумме. DATEDIFF возращает разницу между timestamp'ами в любых единицах. Сгородили какую-то хрень в базе, а оказывается MSSQL виноват. Ну нет там Timespan'ов, это не повод изобретать какой-то странный способ их хранения. Сохраняйте два таймстампа — время начала и время окончания, или длительность в секундах. Какие проблемы-то?
                • –2
                  У меня нет проблем. Я с MSSQL общаюсь посредством EF, и у меня EF в LinqPad отлично делает Sum по TimeSpan, и то что в БД оно мэппится на time — меня не сильно волнует. Тем кто пишет на t-sql я сочуствую, не более.
          • +1
            в interbase/firebird минимум 15 лет уже есть
            Коллега, 10 лет работал с firebird, и сравнивал с другими диалектами PSQL. Да, он хорош, действительно. Но. Сам по себе firebird все-таки сдает позиции когда большая и сложная БД с большим количеством данных и транзакций. Если точнее — требования к виртуозности разработчика растут экспоненциально с линейным ростом сложности БД.
      • 0
        Почему же так категорично?

        Есть проекты, в которых функциональности ХП и в MS SQL будет вполне достаточно. Он, конечно, беднее, чем PL/SQL, так что спектр таких проектов серьезно уже.
        • 0
          А зачем? Нет, правда? Если уж хочется писать хранимки, то есть postgre задаром, есть очень уютный firebird — тоже задаром. Есть oracle на крайний случай. Или можно TSQL, но с ORM типа Entity Framework — база-то сама неплохая, TSQL только хреновый. TSQL просто не нужен.
          • +1
            Зачем — хорошо описано в статье. Почему именно T-SQL — таковы могут быть ограничения проекта. По разным причинам. Вы же тоже хранимку из реального проекта привели — пришлось же написать )
  • +8
    Одним из минусов использования хранимых процедур это значительные трудности поддержания версий кода.
    По крайней мере мы не нашли (кроме очень дорогих) решений для хранения версий PL/SQL функций.
    Может кто в комментариях подскажет.
    • +7
      Впервые слышу о такой проблеме.
      Всегда храню код в файлах, файлы в системе контроля версий.
      Опишите подробней, в чём проблема?
      • +1
        это очевидное решение. А что, бывает по-другому!?
        Можно еще простенькие скрипты написать для командной строки даже, типа «скомпилить такой-то модуль» и т.п.
      • +4
        Попробую сформулировать.

        Допустим необходимо реализовать некую бизнес функцию.
        Для ее реализации требуется изменить какие то таблицы (добавить столбцы к примеру)
        После этого написать непосредственно процедуру обработки данных. В нашем случае, по историческим причинам, это будет PL SQL.
        Соответственно, должна быть основная версия приложения и ответвление для хранения версии новой бизнес функции. По хорошему, все изменения в таблицах, также должны быть зафиксированы в версиях и неразрывно быть связаны с кодом PL SQL.
        У нас пока это не получилось увязать. Просто код PL SQL хранить не проблема, но без связки с изменениями табличной структуры это не очень эффективно.

        Написал, прочитал еще раз… скорее всего эта проблема будет и в случае вынесения бизнес логики из СУБД.
        (Допускаю, что у нас просто порочная практика)
        • +4
          У нас, например, применяется релизная схема. DDL объектов (и таблиц и функций) и скрипты миграции лежат в одном репозитории.
          Я недавно публиковал статью с примером схемы, в которой это так реализовано. В том примере миграции нет правда.
        • +1
          См. liquibase
        • +4
          Вопрос версионирования структуры таблиц возникает вне зависимости от места реализации бизнес-логики. Но он тоже имеет некоторые решения, хотя и не столь хорошие, как для исполняемого кода. Ну а с кодом ХФ вообще никаких проблем нет.
        • +2
          У нас для этого создан отдельный «инсталлятор бд», который в системе контроля версий подхватывает определенные папки со скриптами и пакетами, и ставит их на базу. Есть папка скриптов с DDL, есть папка скриптов с views и т.д. Ну и отдельная папка с файлами пакетов. Все ставится в определенном порядке, пакеты в самом конце. Соответственно, у каждого файла есть своя история изменений, все прозрачно, удобно и автоматизированно.
        • +1
          Такое решение знаю: можно хранить в системе контроля версий текущую схему данных и функции. При деплоее функции накатываются, а между двумя схема автоматически генерится diff из alter-ов и так далее.
      • +1
        Что же это за хранимые процедуры СУБД, если они хранятся в файлах? :)

        А если серьезно, то проблема действительно есть. Даже простое линейное версионирование проблематично, а если говорить о ветках, мержах и т. п., то возможностей обычных VCS (svn, git, hg) недостаточно. Текстовые файлики они поддерживают, конечно, но вот их синхронизация со схемой БД отдельная задача. Приемлемого решения пока не нашёл.
        • 0
          Для MSSQL мы используем редгейтовский SQL Source Control. Вполне неплохой инструмент для наших задач, не такой уж дорогой, плюс, лицензии можно покупать не на всех разработчиков, а только на тех, которые непосредственно много ковыряются в базе и им неудобно, чтобы за них коммитили другие.

          Вот с постгресом всё более грустно, да.
        • 0
          Как здесь выше на пару комментариев написали — стоит сделать систему релизов, которая накатывает патчи на базу, что не так уж и сложно. Дико помогает при разработке, автоматизируя откат и накат обновлений, все изменения хранятся в виде последовательной истории, привязанной к компонентам и это неплохо ложится на проекты разных версий при разворачивании, что особенно ценно, когда их зоопарк выходит за двузначные числа.

          Из недостатков — если нужно работать с реально большой базой при разработке — подобные танцы с восстановлением базы из бэкапа неэффективны и стоит заморочиться с альтернативными способами восстановления. В том же постгресе есть режим копирования базы, можно посмотреть и в сторону создания снапшотов и дельты на уровне файловой системы или виртуальной машины. К счастью, это для большинства проектов неактуально.
    • 0
      Для некоторых СУБД (Firebird SQL в частности) некоторые инструменты (IBExpert, например) вообще прозрачно для разработчика.
  • +1
    Хочу прокомментировать.

    «Размазывание бизнес-логики»: вовсе нет, всю ее можно реализовать в СУБД, или разделить на логичные слои.
    «Скудность языка СУБД»: в Oracle в pl\sql с успехом применяю типы. Да, не так удобно и круто как в Java — зато доступ к данным гораздо проще и органичней. В общем, не такой уж он скудный.
    «Непереносимость хранимых функций»: часто ли реальные проекты гуляют с базы на базу? Особенно такие, где сложная логика.
    «Отсутствие необходимых навыков у команды и высокая «стоимость» соответствующих специалистов»: надуманно. PL\SQL девелоперов полно. Или дайте мне еще денег, раз у меня высокая стоимость:)

    Еще на тему highload скажу… что-то мне кажется, что Oracle решает данную проблему. И наверняка лучше, чем некий программист Вася, который посчитал уместным написать свой сервер приложений на С++, т.к. местный DBA не умеет грамотно настроить Oracle. Но тут могу заблуждаться.
    • +2
      о структуры в Оракле. It makes me cry
      • +1
        там типы, типы есть. С методами, наследованием, конструкторами, деструкторами и прочими прелестями ООП
        • 0
          Типы есть, но я еще ни разу не встречал продукт, который бы использовал «объектно-ориентированные возможности» Oracle. И, если честно, не хочу встречать. Сам использую объекты, только как структуры данных (без наследования). Причем эти объекты существуют только во время исполнения, т.е. в таблицах лежат данные в обычном виде.
          Возможно, конечно, это все от понимания того, что БД — это не вещь в себе, что она лишь часть общей системы. И используемые клиенты часто не поддерживают ничего сложнее простых типов данных и курсоров.
          • +1
            Я, я автор такого продукта!!! Даже бумажка из патентного бюра есть:)
            • 0
              Зачем вы так.
              • 0
                Вся логика в СУБД, но функционала процедур стало явно мало. Выбор: либо делать сервер приложений со всеми вытекающими, переписывать все нафиг, либо использовать возможности ООП в Оракл. Они специфические местами, но если разобраться, именно разобраться — вполне себе юзабельны.
                • 0
                  Вот по мне это уже та самая грань, где надо разделять ЯП и Stored.
                  Я не оспариваю ваше решение, любое рабочее архитектурное решение не принимается просто так.
                  • 0
                    Угу, в общем бывает по-разному. Мы всегда против использования инструментов, которыми не умеем пользоваться. Я согласен что очень редко применяют такое в Оракле. Но считаю, что зря:)
                • 0
                  а примеры можно, какие-нибудь наглядные, по которым видно, что вариант «таблицы + процедуры» хуже, чем «оракловые ООП расширения»
                  • 0
                    Когда логику удобно представить с помощью объектов, а не таблиц и процедур. В конечном итоге все — нолики и единички, все, что до — для удобства и скорости разработки.

                    • 0
                      вот на таком уровне — я все понимаю, да.

                      Есть у меня «под рукой» проектик, у которого если вытащить весь PL/SQL код, то суммарно получится более 300К (более трёх сот тысяч, да) строк кода. И это только package body, procedures, functions. И никакого Oracle PL/SQL OOP Extension там не используется. И вроде бы люди, которые всё это писали грамотные.

                      Полностью мой вопрос звучит так «есть ли хорошие примеры использования ООП в Оракле, которые можно показать разработчикам, что бы они прониклись и начали пробовать сами так делать?»

                      Cам я всеми руками за ООП. По крайней мере, за то, что нужно попробовать сделать так и эдак, и сравнить результаты с разных сторон. Но в контексте СУБД я не разработчик. Мне приходится время от времени это читать и понимать, как оно работает.
                      • +1
                        Ну вы же понимаете, что составление и аргументация такого примера — это скорее тема для статьи среднего размера, нежели чем для комментария. На досуге подумаю, может и напишу такую. Пока лишь буду о личных впечатлениях: мне так нравится больше, код понятней и сгруппирован более логично, можно мыслить в интуитивно понятных объектах — мне нравится.
                        • 0
                          Статья — это было бы очень круто, да.
                          Но для начала, устроили бы просто исходники.
                          • 0
                            Постараюсь найти читабельный короткий пример — чтоб показательно. Но после выходных, даже от дискуссий в интернетах надобно отдыхать:)
    • +1
      Про хайлоад уточню — я говорю про проблемы с горизонтальным масштабированием. Как при использовании ХФ (с ними сделать шардинг сложней, если они были изначально на это не рассчитаны), так и вообще как общая проблема РСУБД.
      • +1
        Ну, значит надо либо изначально рассчитывать ХФ на это, либо отказываться от СУБД:) Но в целом это и правда холивар без конкретной задачи (и известного набора исполнителей, часто это тоже входное условие задачи) сложно оценить подходы.
        • 0
          Однозначно. Но плюсы и минусы из статьи можно использовать как некий чеклист, когда делаешь оценку для своей задачи. Мне кажется может быть полезно.
          • 0
            да, я не спорю же. Статья нормальная, но рамочная.
  • 0
    Соглашусь с автором. Хранимые процедуры вещь мощная их использовать надо осторожно. В одном из проектов я превратил postgresql в RPC сервер, ради эксперимента. Работало бысто, но логика размазалась, были проблемы с дублированием. Еще приходилось администрировать один проект с большим количеством хф. Было очень неприятно что он оказался привязанным к одной конкретной БД.
    Поэтому пришел к мнению что одно из лучших решений для малых и средних проектов использовать ORM или прописать слой абстракции от данных самому.
    К стати ORM в малых и средних проектах справляется с задачей на 80%-90% и экономит время разработки. Другое дело что остаются 20%-10% с которыми ORM' трудно справиться. Обычно это какая-то аналитика с агрегациями. И тут заранее нужно подумать перед выбором ORM как это будет решаться. Я работал над средним проектом в котором ORM был выбран не верно. Из-за аналитики БД погрязла в денормализациях.
  • 0
    При использовании динамического SQL со стороны клиентской программы, клиентская программа передаёт СУБД SQL команды в виде строк, предварительно формируемых в коде.

    При использовании ХФ SQL код на стороне приложения обычно статический, и выглядит, как простой вызов ХФ, параметры которой передаются не строками, а через placeholders (:variable) через механизм binding.

    Механизм binding можно использовать и без ХФ, используя подготовленные запросы (prepared statements).
    • 0
      Можно. Но тут речь о том, что в этом случае никакого SQL injection быть не может (обычно).
      А prepared statement сам по себе строка. Перед тем как вызвать prepare он где то формируется. Там и может быть injection.
      • 0
        Формируется в коде статически. Как может быть инъекция до обработки пользовательского ввода?
  • +5
    Ух, какой пост на пятницу, прогнозирую over 200 камментов.

    Если по делу, то все зависит исключительно, от того где вы это применяете. За 10+ лет работы в финансовом секторе хочу сказать, что я исключительно за применение stored, и вот несколько причин:
    — Необходимо реализовать отчет, который выводит какие-то данные, которые пользователь видит в UI, сгруппировать их по колонке А и сумма по колонке Б. Результат колонка Б рассчитывается в UI. Печаль.
    — Необходимо запустить процесс расчета неких показателей. Лезем в UI в надежде увидеть там call Stored_Proc, а видим там UPDATE, INSERT…. Это ужасно, потому что UI на MS Acceess с VBA, той версии, которою MS уже не поддерживает.
    — Ежедневный расчет витрин данных для отчетности, где расчет идет по остаткам, на счетах которых более миллиона. Только stored, profit!

    Оперируя большим количеством данных, всегда буду использовать только stored, мне так проще, я вижу узкие места в расчете, я знаю, где использовать индекс, а где хинты, а если это MS то временные таблицы. С другой стороны если я буду писать свой уютный бложик или небольшую страничку, которая оперирует небольшим количеством данных и связей, да вполне можно переложить логику на ЯП, и абсолютно не думать какая у тебя БД – MySQL, SQLite, etc…
    • +4
      Согласен с вами. Я бы сказал так:
      1. Если БД «карманная» (используется только одним приложением или даже одним экземпляром приложения), то логику можно перенести в это приложение.
      2. Если БД используется большим количеством экземпляров приложения или приложений/модулей много — однозначно логика должна быть в ХП.
    • 0
      В чём преимущество процедур по сравнению с тем же набором SQL-команд из приложения? Ну, некий оверхид, конечно есть, но имхо, он незначительный.
      • –1
        В случае изменения этого кода новую версию приложения не надо выкладывать.
        • 0
          С одной стороны. С другой, в популярных РСУБД код хранимых процедур не версионируется, насколько я знаю. Если разработка не линейна, то изменения нужно вносить очень аккуратно и возможности откатиться в случае ошибки весьма ограничены.
          • +1
            В файлике. Один хрен скрипт миграции писать, который там же валяется.
            • –1
              Если писать код в файлике, а потом его исполнять в СУБД, то это по сути и будет означать выкладывание нового кода.
              • 0
                Этот код к клиентам может не иметь абсолютно никакого отношения, даже перезапускать ничего не придётся. В случае же, когда код в клиентах — нужно синхронно обновить логику, возможно, нескольких приложений, обеспечить блокировку доступа от несоответствующих версий, что реально усложнит и без того непростую систему, автоматически породит ад версионирования, само обновление не мгновенно происходит и т.п. неприятные моменты, что в свою очередь указывает на то, что бизнес-логика оказалась таки размазанной и мы нарвались на то, от чего бежали.
          • +1
            А почему РСУБД должна поддерживать версионность кода хранимых процедур? Имхо это то же самое, что требовать версионности от компилятора C. Это задача разработчика.
            • –1
              Пускай не РСУБД, а другие инструменты. Но нормальных универсальных инструментов подобных svn/git/hg/… для SQL нет толком.
              • +2
                А зачем «инструменты подобные svn/git/hg/…»?

                Я хранил SQL, T-SQL и pl/sql в svn, в git и в TFS. И, знаете, оно там вполне хорошо хранится.
                А уж если научить скрипт сборки инсталлятора добавлять номер текущей версии в виде комментария в начало каждого скрипта, так вообще замечательно будет.
                • –1
                  Текст схемы СУБД (включая ХП) хранится, да. Но автоматической синхронизации с СУБД нет «из коробки» (или в виде стандартного де-факто расширения). Изменив текст ХП в файле нужно не забыть изменить его в БД. Причём различать ситуации когда процедура создаётся, когда обновляется, когда удаляется. Причём желательно не обновлять процедуры, которые с момента прошлой синхронизации не изменились, предусмотреть возможность отката и т. п.
                  • +2
                    Изменив текст ХП в файле нужно не забыть изменить его в БД.

                    СКВ первична, dev-база крайне вторична. Но вообще при работе с pl/sql через pl/sql developer проблемы с сохранением и в СКВ и в dev-базу у меня не возникает: не сохранишь в базу — не заработает, да и проверка на компилируемость (оно же сохранение в базу) — первое что делается после любых изменений, а Ctrl+S (в ФС => СКВ) за годы разработки уже рефлекс.

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

                    В pl/sql различать это не требуется — CREATE OR REPLACE.

                    когда удаляется

                    Как выяснилось это не такая уж проблема — просто храним все удаления в одном файле и прогоняем при каждом запуске инсталлятора.

                    Причём желательно не обновлять процедуры, которые с момента прошлой синхронизации не изменились

                    Да, для этого надо немного поколдовать над сборкой инсталлятора, но 1 раз и это не rocket science.

                    А откат в общем случае невозможен — для него общих инструментов не написать.
                    • –1
                      А откат в общем случае невозможен — для него общих инструментов не написать.

                      В этом и проблема.
                      Как выяснилось это не такая уж проблема — просто храним все удаления в одном файле и прогоняем при каждом запуске инсталлятора.

                      То есть если нужно что-то удалить, то нужно удалять из двух мест. А если потом нужно восстановить удаленное, то не забыть удалить и списка удаленных. Вероятность что-то напутать повышается.
                      В pl/sql различать это не требуется — CREATE OR REPLACE.

                      Завидую.
                      • +2
                        А где не так? в MS SQL, чтобы не задумываться о наличии или отсутствии ХП в базе, использую стандартный механизм:

                        if OBJECT_ID('myProcedure') is not null
                          drop procedure [dbo].[myProcedure]
                        GO
                        
                        CREATE PROCEDURE [dbo].[myProcedure]
                        (
                        ...
                        
                        • 0
                          В MySQL есть даже более удобный вариант
                          DROP PROCEDURE IF EXISTS `name`;
                          CREATE PROCEDURE `name` ... ;
                          , но всё равно это костыль, имхо.
              • 0
                По моему, перечисленным СКВ все равно что хранить: код java-программы, PLSQL или текст художественного романа. Это уже опять же от разработчика зависит как он туда поместит свой код и как достанет. Есть много решений, которые сканируют базу и обновляют(создают) фалйы в репозитории. Если со всем по крутому, то хорошо создавать объекты БД непосредственно в файлах, организовать собственную структуру каталогов (в этой папке таблицы, в этой процедуры и т.д.) и отдать под контроль СКВ. Сборщиков и миграторов тоже хватает (тот же мега популярный ликвибэйс например и другие). А вот с инструментом, который бы смог организовать структуру хранения объектов в файлах, легко добавлять их и легко извлекать, имхо проблема. У меня есть пару идей и наработок на эту тему, если интересно поделюсь.
                • –1
                  Не всё равно. С кодом java-программы всё относительно просто, а вот с художественным романом уже сложнее, поскольку зачастую они хранятся не в виде чистого текста, а в какой-то разметке. Хорошо если с текстовой типа html/xml, а если бинарный формат типа .doc (не уверен, но он вроде бинарный). С SQL (хоть структура таблиц, хоть ХП) тоже сложнее — команды описания БД не декларативные (как в случае с мэйнстримовыми ЯП), а императивные, плюс инкрементные, учитывающие предыдущее состояние БД. Проще говоря, нельзя просто сказать СУБД «хочу БД с такой структурой и процедурами», а она сама где нужно создаст, где нужно обновит, а где нужно удалит соответствующие объекты. Если нам не нужна больше таблица/ХП, то нам нужно дать явно команду DROP, если нужно изменить, то нужно дать явно команду ALTER, если создать, то CREATE. В итоге мы оперируем не структурой, а инкрементными миграциями, в предположении, что точно знаем состояние БД на момент их запуска, что в случае разработки в нескольких ветках параллельно, далеко не факт (даже если забыть про возможность ручных правок из различных клиентов). Различных попыток автоматизировать процессы достаточно, но вот таких инструментов, чтобы можно было забыть совсем про проблему, как-то не встречались. Тот же ликвибэйс оперирует ченжсетами, подразумевающими, что текущее состояние БД мы знаем.
                  • +1
                    Так это уже разговор не о ХП, а о БД в целом.

                    ХП дополнительной сложности не вносят — для них не нужны миграции. Их можно вообще все пересоздавать при накатывании обновления (если бы времени не жалко было).
                    • –1
                      И если были изменение вручную в базе, то они затрутся. Нужен развитый инструмент, который бы предусматривал возможность разрешения конфликтов.
                      • +1
                        Что значит 'изменения вручную'? Если работаете с исходниками, то никакой общей рабочей эталонной базы может и не быть. У каждого разработчика может быть своя база, которую он соберет из исходников, и что он там в ручную сделает — никто не узнает, главное — что будет в репозитории.
                        • 0
                          Кроме разработчиков могут быть и админы, у которых могут быть срочные задачи, не оставляющие время на правки исходников, процедуры деплоя и т. п., и которые вносят изменения прямо на рабочую базу. Без инструмента разрешения конфликтов такие вещи будут приводить к потерям.
                          • 0
                            Тут наверное стоит разделять разработку от администрирования.
                            • 0
                              Разные цели, разные инструменты, разные люди с этим работающие. Все, о чем говорилось это про разработку конечно.
                              • 0
                                Но работают все с одной базой и удобных универсальных инструментов для разрешения конфликтов особо не наблюдается.
                          • +1
                            Если админ втихушку внесёт изменения в обход СКВ, а потом они затрутся — придётся опять то же самое чинить.
                            Конечно ДБА тоже свои изменения в СКВ ведут, это ж DevOps :)
                            • 0
                              Чинить-то придётся, но виноват будет тот, кто набрал ./deploy.sh
                              • 0
                                а почему не тот, кто незадокументировал изменения в БД?
                                ведь не хотите же вы сказать, что у вас ORM-код при деплое меняет структуру БД под себя?..
                                • 0
                                  Потому что никто такого документирования не требует. Подразумевается, что перед накатыванием обновлений конфликты будут выявлены и разрешены.

                                  Ну и ОРМ можно сказать, что меняет.
  • +6
    Еще есть скудость инструментария. Например, есть ли Resharper для TSQL?
    • 0
      Кстати, да! С Ms я мало общался, для Oracle проблем ни с ide, ни со статическим анализом нет, а вот для Pg нормальной IDE так и не нашел, все что есть — либо убоги, либо заточены только под администрирование, а нормальную разработку вести неудобно.
      • +1
        ясное дело pgAdmin3 не сравнить с ToAd, но мне его вполне хватает. А вот Microsoft SQL Server Management Studio, мне нравится даже меньше чем pgAdmin3.
        • 0
          Были бы в нём хотя бы вкладки. Замаялся альт-табом окошки сканить :)
          • 0
            это да
        • 0
          Функциональность MS SQL Management Studio достаточно эффективно расширяется аддонами. Я не очень глубоко погружен в SQL-разработку, как правило, за раз пишу не более 1 ХП, так что мне и стандартной функциональности хватает, но о возможностях расширения слышал.
          • 0
            Да меня все устраивает и без аддонов, но те же вкладки на 17” мониторе после открытия 4 вкладок, они начинают прятаться, и после этого я уже не знаю, что хуже, такие вкладки или окна pgAdmin3. Просто я видимо сильно привык к Toad.
            • 0
              Я года три назад вернулся на MS SQL с Oracle, поэтому слегка подзабыл, как там в Toad.
              Да, вкладки в студии широковаты, но при большом количестве можно всегда вкладки вывести списком (на строке с вкладками кнопочка есть), поэтому такое поведение меня если и напрягает — то не критично.
              • 0
                а можно поподробней как вывести списком, что за кнопка?
                • 0
                  Вкладки складываются слева, а справа в этой же строке есть кнопка «Закрыть вкладку» и «Открытые вкладки» — нажимаете ее, все вкладки (в том числе и скрытые) вываливаются одним списком.
                  Это я про MS SQL Studio 2008 говорю, в других может выглядеть по-другому, но функционал вроде во всех студиях есть.
                  • 0
                    такая кнопка есть, я просто подумал, что тот список можно вывести куда-то отдельно. просто студия еще после этого списка начинает странно сортировать вкладки.
                    • 0
                      Отдельно — это разве что «Alt+W,W» (W два раза с зажатым Alt) — откроется список открытых вкладок :)
            • 0
              Я купил TabStudio add-in, он вообще для Visual Studio, но MS SQL Management Studio на том же движке, так что проблемы с табами решились и там. Плюс, где-то в внастройках самой студии можно чуток сократить ширину табов, убрав из заголовков имя базы.
      • 0
        В принципе, IDE от JetBrains интегрируются с Postgres — поэтому необходимость лазить по pgadmin3 по большей части снимается, не говоря уже о плюшках написания кода, версионирования и прочего )
  • 0
    При использовании ХФ SQL код на стороне приложения обычно статический, и выглядит, как простой вызов ХФ, параметры которой передаются не строками, а через placeholders (:variable) через механизм binding. Конечно это не исключает возможность SQL injection полностью (ведь можно умудриться в ХФ конкатенировать строку, переданную параметром с текстом динамически выполняемого SQL запроса), но значительно уменьшает её вероятность.

    Вот тут можно по-подробнее??? Я не так давно переносил часть бизнес логики в ХФ, и насколько я помню в MySQL такой фокус не проходит. Можно передать динамический запрос в качестве параметра, но это ничего не даст.
    • 0
      Не скажу за MySQL, но в Oracle и PostgreSQL всегда можно что то типа:
      create procedure p(inject_me in varchar2)
      is
      begin
       execute immediate inject_me;
      end;
      
      • 0
        Точно, что-то припоминаю (больше года с ораклом не работал, уже подзабыл). Но в MySQL (во всяком случае в моей версии) всё несколько иначе. Во всяком случае провести инъекцию в ХФ типа этой у меня не получилось, в частности (если мне не изменяет память) из-за того что EXECUTE выполняет только один запрос за раз.
         SET @updatequery :=CONCAT('UPDATE table.users u set u.STORED=(SELECT COUNT(*) FROM db.local_map_base_',@user_name,' lmb) WHERE u.GUID_USER=?; ');
            PREPARE nmut FROM @updatequery;
            EXECUTE nmut USING @user_guid;
        
  • 0
    Вывод такой, что чёткого алгоритма нет. Каждый раз решение остаётся за архитекторами и менеджером

    Не то чтобы я был против участия Капитана Очевидность, но на деле каждый раз так и происходит. У меня сфера использования БД узкая (администрирование фотокаталога), хранимых процедур тем не менее полтора десятка. Но при любой попытке расширить функциональность встроенного языка мы ИМХО рано или поздно получим на выходе Кобол.

    И тем не менее. Узкости языков высокого уровня на ранних этапах развития прекрасно преодолевались директивой asm. Мне почему-то представляется, что аналог такой директивы, позволяющей включать в ХФ элементы универсальных ЯВУ, сильно выручили бы многих.

    Моё ИМХО, конечно же.
    • 0
      Никто не мешает писать хранимки на Java в Oracle, или на Python, JS и прочих в PostgreSQL.
      Но моё ИМХО — если логика такая сложная, что реально требуется язык общего назначения — надо выносить.
      • +2
        Да не дай бог. Видел одно вендорное решение с java в БД. Никаких шансов на анализ производительности. Если SQL еще можно вдоль и поперек статистикой исследовать, то подобный код просто крест.
      • +1
        Дополню — или на C# в t-sql. Если мне память не изменяет, SQL Server с 2005 версии позволяет встраивать CLR (В виде функций или ХП).

        Правда, проблем там зачастую больше, чем пользы, поэтому используется такой функционал относительно редко.
        • +1
          Память не изменяет. Но заморочек, связанных с ограничениями и правами доступа там тоже хватает в этом случае.
          Правда иногда CLR встраивается от незнания некоторых штатных возможностей. Равно как и некоторые хранимки делаются по той же причине, в то время как что-то можно сделать на уровне имеющегося инструментария.
  • +2
    По-моему, Кайт сказал, что лучше программист, который хорошо знает SQL и плохо PL/SQL, нежели наборот.
    Из собственных наблюдений не могу согласиться или опровергнуть, но точно есть 2 проблемных пункта с засовыванием логики в БД:
    1. PL/SQL все же ограниченный по сравнению с той же java язык, поэтому часто видны костыли и извращения ( опять с точки зрения java). Нередко это приводит к ошибкам или очень медленным решениям. Про удобство восприятия кода PL/SQL вообще молчу.
    2. Размытая логика.
  • +1
    При использовании динамического SQL со стороны клиентской программы, клиентская программа передаёт СУБД SQL команды в виде строк, предварительно формируемых в коде

    Не понял, вы про ORM говорите, или про «SELECT * FROM » + tableName + " WHERE..."? Если про ORM, то во многих защита от SQL Injection есть из коробки. Попробуйте сделать SQL Injection через тот же Entity Framework.
    • –1
      Про ORM. Они формируют строку SQL примерно так же, конкатенацией. Я не говорю, что во всех ORM есть уязвимость, я говорю о том, что её вероятность сравнительно с вероятностью инъекции при вызове ХФ высока. В первом случае есть потенциально опасное место — склейка строк, во втором — нет.
      • +1
        Классический пример. Есть таблица, для которой на UI куча фильтров — по дате там, названию, и еще десяток. Если сделать один параметризированный запрос, куда насовать что-то типа where (nameFilter is null or name like nameFilter) and (… остальные фильтры… )
        — оптимизатор не справится, т.к. планы надо на каждый набор фильтров разные, а планы эти кэшируются.

        С хранимками это выльется в dynamic sql, и борьбу с sql injection вручную.

        На linq2sql это выльется в строго-типизированный код типа:
        if (nameFilter != null) query = query.Where(row => row.Name.Contains(nameFilter))
        — все это строго-типизировано, будет генерировать запросы под конкретный набор фильтров, и никаких sql injection там не будет by design.

        Так что где безопаснее — это еще бабушка надвое сказала.
  • 0
    Раньше в подобных статьях всё время в качестве плюса использования хранимых процедур, приводилась отсутствие необходимости делать изменения в коде программы (не нужно обновлять приложение у клиента), а достаточно лишь изменить процедуру в БД. Так вот в большинстве случаев это неверно.

  • +4
    Самый жирный плюс ORM это — отделение прикладного кода от механизма хранения данных. Можно между прикладным кодом и БД прозрачно для прикладного кода напихать аудита, всякого кэширования, фильтрацию по правам доступа, например.

    Короче с правильно приготовленным ORM код получается намного более высокоуровневым. Да, все это часто за счет производительности. Но как показывает практика, большая часть проблем с производительностью решается на уровне ORM, а что не решается — никто не мешает точечно делать быстрые хранимки.
    • 0
      отличный комментарий!
    • 0
      Ничто не мешает сделать такое разделение в бд, на уровне хранимок. Есть слой процеур для низкоуровневой работы с данными, есть слой бизнесс логики и т.д.
      • +1
        Мешает то, что языки в БД не имеют средств чтобы строить хорошие абстракции.

        У меня вот, например, на реальном проекте в ORM-ке во многих таблицах есть createdby/whem/modifiedby/when, это строчек в 10-20 кода сделано и совершенно прозрачно для прикладного кода — т.е. нельзя забыть заполнить, нельзя заполнить неправильно. В БД можно такое сделать триггерами, но пришлось бы вешать по триггеру на каждую таблицу.

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

        Также можно делать денормализацию, кэширование, аудит, раскладывание данных по разным базам, всякие push-нотификации. И все это — без вмешательства в прикладной код.
        • 0
          Мешает то, что языки в БД не имеют средств чтобы строить хорошие абстракции.

          У меня вот, например, на реальном проекте в ORM-ке во многих таблицах есть createdby/whem/modifiedby/when, это строчек в 10-20 кода сделано и совершенно прозрачно для прикладного кода — т.е. нельзя забыть заполнить, нельзя заполнить неправильно. В БД можно такое сделать триггерами, но пришлось бы вешать по триггеру на каждую таблицу.

          А вот это имхо не очень сильный аргумент. В более менее крупных приложениях БД редко когда размазывают инсерты (апдейты, делиты и т.д.) по коду. Так же определяют слой доступа к данным, т.е процедуры, у которых могут быть такие же поля как вы описали, тоже ничего не забудешь и не ошибешься (проверки, значения по умолчанию). А вот триггеров (по крайней мере в тех проектах в которых учавствовал) стараются избегать, а если и делают, то вызываются в них те же самые хранимые процедуры.
          Но соглашусь, что в ORM это более развито, больше инструментов, готовых решений, а в базе многое приходится делать и продумывать самостоятельно.

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

          То же сомнительный аргумент. Помимо вьюшек (кстати весьма мощный и гибкий инструмент) есть хранимые функции, которые могут возвращать наборы данных (а их в свою очередь можно использовать в других функциях). Если это Оракл, то есть специализированная штука DBMS_RLS, заточенная под разделение прав лучше (т.к. это родное решение для базы) любой ОРМ.

          Также можно делать денормализацию, кэширование, аудит, раскладывание данных по разным базам, всякие push-нотификации. И все это — без вмешательства в прикладной код.
          Все вышеперечисленное входит в большинство современных промышленных СУБД (в Оракл уж точно).

          Т.е. альтернатива есть всегда.
        • +2
          И вообще я считаю, что должно быть не «ORM vs stored procedures», а «ORM+stored procedures», т.е. есть своя инфраструктура на стороне базы (набор базовых процедур для работы с данными, набор процедур бизнесс логики, функции(вьюхи) для представления данных и др.) и есть ORM, которая предоставляет объектную обертку (и не только).
          Т.о. используется мощь ОРМ, и БД не представляет собой набор плоских тупых таблиц, а используются все ее специализированные функции.
  • 0
    кроме Java в Oracle или Python в postgresql, хранимки можно писать еще и на C# в MS SQL
    en.wikipedia.org/wiki/SQL_CLR
  • +2
    Честно говоря, не считаю что есть особый конфликт между использованием ORM и реализацией логики в базе.
    В своих проектах примерно 90% логики реализую через ORM, оставшиеся 10%, которые требуют особой оптимизации выношу в базу данных и также вызываю через сгенерированные ORM функции-обертки. При этом не страдает ни объектность (результат всегда строго типизирован) ни производительность (логика исполняется в БД максимально используя все ее возможности).
  • +1
    У нас игрушка (уже вторая космическая стратегия) почти полностью на PSQL написана — только космический бой реализован на С++, так как с кучей динамических массивов на PSQL работать неудобно :)
  • +6
    В наших проектах используем исключительно ORM.

    В случае написание бизнес логики на C#/.net мы можем использовать полноценный ООП подход к проектированию и разработки.
    Применять удобный рефакторинг (R#), IoC, кэширование на уровне сервера приложений (который потенциально масштабируется сильно проще чем БД).
    Организовать качественное тестирование, измерять покрытие кода тестами, метрики и т.д.

    Да, можно писать на C# и встраивать это в sql server, но это даст лишь часть возможностей.

    Тем кто за ХП/Ф — расскажите как вы применяете IoC, тестируете, узнаете покрытие кода, контролируете метрики, думаю это может быть многим интересно.
  • +5
    Ок, подолью огня. Я — системный архитектор нескольких информационных систем.
    Считаю, для промышленного написания бизнес-логики удобно использовать статически типизированные, объектноориентированные языки, на которые есть хорошие инструменты:
    * unit тестирования
    * статического анализа и анализ метрик кода
    * отладки

    Java, Python, C# — хороший выбор.
    T-SQL — в этом смысле плох. Про PL/SQL не знаю, есть ли в нем указанные инструменты и действительно ли он ООП или же как PHP.

    Также замечу, что описанные плюсы ХП по сравнению с ORM в большинстве ошибочны. Сравню с .C# и DataObject.Net, т.к. пользуюсь ими.

    * Скорость
    Значимое отличие в скорости будет проявляться только за счет:
    1. Константных задержек передачи по сети и материализации.
    2. Использования редких средств SQL, не поддерживаемых большинством ORM, например такие как оконные функции.
    Первая ситуация значима для разовых или очень быстрых запросов до нескольких мс, которых, как правило, не много.
    Вторая — специфична, и в большинстве запросов бизнес приложений не проявят себя.
    Ну и есть еще дискуссия по поводу качества запросов ORM. Я, например, занимался анализом запросов от DataObject и могу сказать что их качество скорости выполнения очень высоко, как бы при этом не выглядел пугающе внешний вид.

    * Сокрытие структуры данных
    LOL. Как раз, когда структура в модели классов, то происходит сокрытие способа хранения, и не придется изменять приложение, например, при смене способа организации наследования Single/Class/Concrete Table Inheritance.
    При этом, при действительной необходимости изменения МОДЕЛИ, например, в C#, 99% работы за тебя сделает Resharper и статическая типизация.

    * Гибкое управление правами доступа
    Опять мимо. В слое доступа к данным можно организовать сколь угодно сложную логику ограничения доступа и она будет прозрачна для кода бизнес-логики.

    * Меньшая вероятность SQL injection
    ORM защищает от SQL инъекций лучше, т.к. делает это централизованно в LINQ провайдере. В хранимых процедурах придется за этим следить постоянно, либо вообще не использовать динамический SQL, что также уменьшает количество инструментов разработчика.

    * Повторное использование SQL
    В наличии, и даже более развито. См. деревья выражений и LINQ в .NET

    * Простая отладка SQL
    Большинство описанных минусов отсутствует. В наличии только действительно более сложная трассировка, анализ качества запросов и внесение изменений по результатам анализа так, чтобы исправить проблему.
    • +4
      Всё так.

      Про скорость еще добавил бы что засчет более высокоуровневного кода, с ORM есть ходы, которые бывает сложно сделать с кодом на хранимках — всякое кэширование, шардинг, денормализация, разделение данных по базам (например транзакционные — в одну, всякие аудиты и логи — в warehouse).

      С хранимками такие ходы скорее всего потребует перетряхивать всю бизнес-логику. Базист прежде всего всю базу покроет микро-оптимизациями запросов, менять схему данных на живом проекте с хранимками — ад. ORM-щик — пойдет и приделает прозрачно для прикладного кода какой-нибудь шардинг. Или скажем сделает отдельную БД для отчетов, и фиг с ним что может где не супер-оптимально. Кстати от этой разницы подхов, думаю, и возникает не понимание — типа «а как я в вашем LINQ хинты оптимизатору буду расставлять?»

      Самый серьезный минус ORM для меня — это все что вытекает из того, что данные не локальны: больше приходится вынимать из БД; приходится юзать lazy loading, либо планировать и вытаскивать в один запрос все что будет нужно дальше — все это усложняет логику.
  • +1
    Холивар? Просто ложка хороша к обеду.

    Вот я вижу ТЗ, которое выливается в десяток средней замороченности формочек, 1 репозиторий и к нему четыре десятка ХФ похожих как близнецы-братья, фигову кучу триггеров и маленькую кучку энтити классов, к которым напрямую биндится UI. И оценку на реализацию этого чуда в 1500 (полторы тысячи) человекочасов. У меня возникает устойчивое желание поменять команду или подрядчика. И разговоры про «стоимость поддержки», «разделение на уровни» и «производительность» на меня решительно не влияют. Это job security код, и точка.

    Вот я вижу другой проект, а там тоже хотят больше 1000 часов на «оптимизацию маппига ORM», который ко всему прочему прицелен на конкретные версии EF и SQL server. Начинали с code first, нарисовали кучу linq запросов везде по коду и теперь блеют про то, что реляционные базы вообще-то отстой и пора переходить на NoSQL. Тут диагноз, думаю, тоже понятен.

    Императивная и реактивная логика пишется в коде приложения. БД это не код приложения(*). Простите, это так и тут нет «за и против». Если вы следуете CQRS или хотя бы как-то изолируете выборки и комманды, поменять императивный код на ХФ когда она будет нужна и оправдана проблем не составит.

    (*) Если вы не Оракл. А если вы Оракл, то, наверное, это меньшая из ваших проблем.
  • 0
    да, холивар, когда смотреть на проблему, находясь внутри, но ведь лучше смотреть извне :).
    Проблему надо рассматривать в контексте архитектуры приложения, с точки зрения разграничения слоев, а не в плюсах или минусах использования ХФ.
    Мощнейший аргумент за и даже действенное правило, когда точно надо писать ХФ это вопрос целостности данных. ХФ также, например, прекрасно справляются со множеством проблем «атомарности» сложных бизнес-операций.
    Но да, проблем и с версионностью и с масштабированием и с тестированием не заметить нельзя, конечно.
    • 0
      А можно по-подробней о проблеме целостности данных? Какой именно аспект?
      • 0
        Скажем, целостность денормализованных данных.
        • 0
          Какой аспект? Требования ACID выполняет СУБД, ORM ее использует. Соответсвено, изменения денормализованных данных будут происходить в рамках одной транзакции и их целостность гарантирует СУБД.
          Или есть мнение, что если код копирования данных написан в триггере, то это более надежно, чем два update в батче?
          • +1
            Да, надежнее. В одном месте поставили два апдейта, а в другом забыли (или не знали) и, например, агрегирующее поле одной таблице не соответствует агрегируемым полям в другой.
            • 0
              В ORM связь будет заданна на событии обработки изменения объекта какого-то класса. Соответственно, «забыть» после этого может только ORM. Но это из того же класса, что MSSQL «забудет» выполнить триггер.
              • 0
                В ORM может не быть событий архитектурно. К тому же мы вроде говорили про два update в батче, что, по-моему, означает отсутствие высокоуровневых абстракций над SQL и работу с базой напрямую.
                • 0
                  Если нет событий или другого аналога, то это печально и тут действительно могут быть проблемы. Но события — есть.
                  Два update в батче сделает, понятно, ORM.
              • 0
                представьте, например, что ваша база данных служит источником данных больше, чем для одного приложения. Сохраняя часть бизнес-логики в ХФ вы, кстати, не только обезопасите себя от разных трактовок одной и той же логики разными программистами, но и суммарно кода у вас будет меньше, меньше ошибок, легче сопровождение.
                • 0
                  А трехзвенную архитектуру, application server и прочие service layer с api нафига придумали?
                  • 0
                    одно не отменяет другого.
                    у нас в предусловиях дискуссии ничего не дано про архитектуру и уровень сложности приложения.
                    кстати и «service layer с api» в реальной жизни вполне себе может содержать ошибки логики, которых, используя ХФ, можно с большей вероятностью избежать.
                    • 0
                      Каким образом, простите?

                      На service layer я могу написать юнит-тесты, быстро развернуть/свернуть на сервер для отладки (ну и см. выше все комментарии про бизнес-логику НЕ в хп).
                      Как мне это сделать с ХП?

                      Ну и лично в моей практике с системами с большим количеством логики в ХП большая часть ошибок была именно там.
                • +1
                  представьте, например, что ваша база данных служит источником данных больше, чем для одного приложения.


                  контр-вопрос. представьте, например, что ваше приложение использует более одной БД. А теперь представьте, что эти БД ещё и на разных (Р)СУБД находятся.

                  обезопасите себя от разных трактовок одной и той же логики разными программистами

                  для этого придумали не только ORM, но и Domain layer и Service Layer — программисту, который работает с моделями через сервисы должно быть всё равно как сервис получил эти модели. Одновременно с этим, сервис может использовать более одного DAO для получения нужного результата.
  • –2
    При использовании ORM на самом-то деле нет гарантий целостности данных.
    Используя же саму базу для реализации логики можно в одну транзакцию уложить множество действий, тем самым обеспечить и целостность данных и повысить скорость отработки.
    • 0
      Какую ORM вы имеете в виду? Все известные мне позволяют управлять сессиями, транзакциями и уровнями изоляции в транзакциях.
      • 0
        не надо привязываться к реализации. Просто, вынося логику на уровень выше, вы потенциально получаете еще одну дополнительную точку отказа.
        • 0
          тот же Hibernate, при режим validate/update и при правильно связанных сущностях, пытается создать (если для его DB-пользователя хватает прав) не только таблицы, но я связи между ними, тем самым, как раз и добавляя на уровне БД дополнительную «проверку целостности и повышения скорости отработки».
  • 0
    Спор можно вести бесконечно и не прийти к консенсусу никогда. Так как данный спор из разряда: какая коробка лучше механика или автомат? Или какая машина лучше: джип или спорт-кар? Или из детства: кто-кого забьет Шварц или Брюс Ли?
  • 0
    Согласен. В каждом подходе есть свои плюсы и минусы. Но в последнее время технологии развиваются в сторону универсальности, повышения эффективности разработки. Думаю что в будущем будет ощутимый перевес в сторону того же ORM.
  • +1
    Хранимые функции
    Как-то традиционно устоялось «хранимые процедуры»/«stored procedures»
    • 0
      Процедуры есть не во всех РСУБД, а ф-ии во всех.
      • +1
        Это вопрос терминологии. Я не со всеми РСУБД имел дело, но в тех, с которыми имел, это называлось procedure, не function. Разве в каких-то СУБД иначе?

        А в общем случае да, процедура — частный случай функции, не возвращающей аргументов.
        • 0
          В MySQL и в PostgreSQL есть как procedure, так и function. Чем отличаются вы знаете :)
          • 0
            В PostgreSQL есть procedure? Может я что то пропустил, но не так давно самым близким к процедуре были функции возвращающие тип void.
            • +1
              любая процедура это частный случай функции.
              иначе: любая функция может быть вызвана как процедура, если вам всё равно на то, что она вернёт.

              аналог «из народа»: «каждый окунь рыба, но не каждая рыба окунь»
            • 0
              Да, погорячился чутка. Наш генератор трансформирует процеду в войд функции.
        • –1
          Ну я б ещё поспорил кто там чей частный случай (MS SQL).
  • 0
    Из своего опыта могу сказать, что при использование хранимых процедур очень просто распараллелить задачу:
    1) делаем спецификацию хранимых процедур и создаем их в виде «заглушек»;
    2) дальше одни пишут (тестируют и т.д.) хранимые процедуры, в то время как другие пишут «интерфейсную/прикладную» часть.

    • +2
      Мне кажется, это относится к чему угодно, у чего есть интерфейс :)
    • 0
      Тоже самое можно сделать при, например, использовании паттернов типа MVC. Особенно хорошо, если ORM достаточно гибкая, чтобы скрывать за интерфейсом модели нюансы схемы БД.

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