Автор принял для себя решение
Автор принял для себя решение

В этой статье я не намерен обидеть DBA-разработчиков. Однако, я пришел к выводу, что в большинстве случаев написание хранимых процедур стало нецелесообразным. В современных реалиях взаимодействие с ними вызывает невообразимую боль и нежелание когда-либо снова с ними работать. Это связано с тем, что требования к качеству работы приложений возросли, а новых инструментов в SQL для решения этой проблемы так и не появилось, что не позволило повысить качество.

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

Но это если вкратце, давайте разберем по подробнее.

Размазанность бизнес логики

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

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

Возможное обращение к отцам основателям хранимых процедур
Возможное обращение к отцам основателям хранимых процедур

В своей работе я довольно часто сталкивался с подобным. Главная проблема заключается в том, что, собирая информацию по крупицам, приходится вносить изменения в код без полного понимания, надеясь, что всё заработает. Если не заработает, приходится пробовать менять что-то ещё, и так цикл повторяется до тех пор, пока ошибка не будет исправлена.

Сложность в тестировании

Начнем с самого популярного вида тестирования – ручного, и, возможно, единственного при использовании хранимых процедур. Часто при ручном тестировании нет возможности поднять локально мега-мастер базу данных со всеми необходимыми данными для тестирования, и приходится обращаться к тестовому стенду, который часто работает нестабильно.

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

Умные мысли не покидали его...
Умные мысли не покидали его...

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

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

Но неработающий тестовый стенд — это ещё не самая худшая ситуация. Гораздо хуже, когда просто непонятно, как протестировать. Например, если хранимая процедура принимает входным параметром XML с динамическим количеством свойств, и вам нужно собрать нужную комбинацию этих свойств, чтобы добраться до вашего участка кода. Очевидно, что никто не осведомлён о необходимой комбинации. Сложность сбора этой комбинации свойств сродни сбору флеш-рояля в покере, когда вы играете на свой последний доллар.

Примерно так ощущаешь себя во время попытки протестировать
Примерно так ощущаешь себя во время попытки протестировать

Автоматическое тестирование или unit-тесты

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

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

Ответ дан после тестирования хранимок.
Ответ дан после тестирования хранимок.

Отсутствие современных фишечек и фреймворков и соединение жизненного пути с БД.

Например, от бизнес-партнеров поступило требование собирать метрики по определённым участкам бизнес-процесса для их оценки. И как мы будем это делать? Правильно, реализовывать ещё больше костылей, потому что SQL — это язык для агрегации данных, а не язык для описания бизнес-логики приложения. В любом современном языке программирования достаточно подключить какую-либо библиотеку и настроить URL /metrics для сбора необходимых метрик и отправляться снова смотреть видосики на Youtube VK video.

Также, описывая бизнес-логику нашего приложения с помощью SQL, мы перекладываем все вычисления на сервер базы данных, что лишает нас возможности легко горизонтально масштабировать приложение с помощью увеличения числа инстансов в Kubernetes.

И вообще современных приложениях стараются отвязаться от конкретного источника данных, ведь кроме конкретной SQL БД существуют и другие. При использовании хранимых процедур наша БД становится конечной остановкой. Нужен более гибкий и быстрый поиск, где можно было бы использовать Elasticsearch? Требуется быстрая NoSQL для какой-либо части нашего приложения, например Redis? Извините, но у нас так не принято.

Хранимка извиняется и выносит предупреждение
Хранимка извиняется и выносит предупреждение

А работая на .NET часто бизнес-логика выноситься в домен и покрываться сразу Unit-тестами не опираясь на используемую базу данных, ведь есть такие прекрасные вещи, как ORM (Entity Framework), которая позволяет выбрать любую базу данных в моменте и наслаждаться жизнью. То-есть мы уже вольны использовать, что нам нужно.

Также стоит упомянуть про логирование, которое работает из коробки в большинстве приложений и отправляется в ELK, чего SQL не может предложить.

Проблемы с переездом на другую БД

Раньше этот пункт казался чем-то невозможным: если понадобится, выделим пару десятков лет на переезд. Но после известных событий и наложенных санкций компании столкнулись с требованием перейти с MS SQL на другие СУБД. И тут начались проблемы: Postgresql имеет другой синтаксис и не обладает теми же функциональными возможностями. Поэтому в кратчайшие сроки нужно всё переписывать и переносить бизнес-логику обратно в приложение.

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

Пример разного уровня экспертизы...
Пример разного уровня экспертизы...

Заключение

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

  • Сложность в поддержке и тестировании.

  • Проблемы при параллельной разработке

  • Ограничение функциональности, функциональностью БД

  • Отсутствие горизонтального масштабирования

  • Отсутствие гибкости в выборе необходимого хранилища данных

  • Проблемы с переносимостью бизнес-логики

  • Backend разработчики должны обладать знаниями DBA

  • Сложность порождает низкое качество и большое количество ошибок.

Понятно, что узкая область применения хранимых процедур все еще существует. Однако для написания бизнес-логики приложения они становятся рудиментом, от использования которого вы не получаете плюсов, а только минусы и желание сменить работу или начать жизнь с чистого листа.

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


  1. rinace
    06.08.2024 10:23

    Обсуждалось 4 года назад

    «В карантин нагрузка выросла в 5 раз, но мы были готовы». Как Lingualeo переехал на PostgreSQL с 23 млн юзеров https://habr.com/p/515530/


  1. rinace
    06.08.2024 10:23

    SQL — это язык для агрегации данных, а не язык для описания бизнес-логики приложения

    Дальше читать не имеет смысла


    1. michael_v89
      06.08.2024 10:23

      https://en.wikipedia.org/wiki/SQL
      "The scope of SQL includes data query, data manipulation, data definition, and data access control."


      1. orefkov
        06.08.2024 10:23
        +1

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


        1. michael_v89
          06.08.2024 10:23

          Но процитировано-то утверждение со словом SQL.


  1. TMTH
    06.08.2024 10:23
    +4

    Насчёт тестирования. Автор наверное забыл упомянуть про возможность тестов на реальной СУБД? Я не то, чтобы фанат хранимок, но надо как-то реальные недостатки обсуждать, а не проблемы возникающие от незнания/неумения.

    Проблемы, описанные в разделах "Сложность в тестировании" и "Автоматическое тестирование или unit-тесты" в значительно мере могут решены связкой test-containers + [ваш любимый инструмент миграции] (для распространённых СУБД всю конструкцию можно запустить за день с нуля). В этом случае, интеграционный тест на хранимку собственно и будет её актуальной документацией.


    1. michael_v89
      06.08.2024 10:23

      Автор наверное забыл упомянуть про возможность тестов на реальной СУБД?

      Это интеграционные тесты, а он говорит про юнит-тесты.

      могут быть решены связкой test-containers + [ваш любимый инструмент миграции]

      Он же не сказал, что они не могут быть решены, он говорит, что это сложнее. В этом я с ним согласен.


  1. oldlama
    06.08.2024 10:23
    +11

    Совершенно правильные замечания с точки зрения казуального юзера, взаимодействующего только с верхними слоями БД. Конечно в отношении "сервис - база данных" не должны участвовать хранимки.
    Однако не стоит забывать, что то, что вы видите от БД это лишь верхушка айсберга. Как известно на поверхности находится лишь 1/10 часть.
    Само формирование базы не представимо и полностью невозможно без использования хранимок.
    Если переводить в метафору - вы, в данном случае, владелец кредитного китайце в пробке, рассуждающий о том, что механическая коробка передач более не нужна и вообще морально устарела. А теперь представьте лицо профессионального гонщика (или Вин Дизеля), который бы это услышал. И вас понять можно, но и глупость, которую вы морозите, сложно оправдать. Не стоит обобщать весь мир АйТи под ваш опыт.

    *Смайлик клоуна*


    1. michael_v89
      06.08.2024 10:23

      Встречал много проектов с базой без каких-либо хранимок. Так что представимо и возможно.


      1. GlukKazan
        06.08.2024 10:23

        Встречал много проектов с хранимками, так что и это возможно тоже.


        1. michael_v89
          06.08.2024 10:23

          Я и не говорил, что проекты с хранимками невозможны.


  1. merkel
    06.08.2024 10:23
    +2

    мне, как .net разработчику, перешедшему в команду с достаточно большим количеством данных, прекрасно понятны мотивы, побудившие автара на написание сего опуса. Было бы все так просто, работоли бы все на одном "самом лучшем языке программирования". Если в приложении есть нормальный бэкэнд, очевидно - не стоит размазывать логику между ним и БД. Но в реальной жизни, не все ПО строится по трехзвенной архитектуре - есть целый пласт задач, где BLL на уровне СУБД суть рациональное решение.


  1. Arm79
    06.08.2024 10:23
    +3

    Любой инструмент должен быть применен по назначению. В том числе и хранимые процедуры.

    Текст процедур вообще ничем не отличается от текста на, к примеру, Java. Его можно прекрасно положить в GIT и так же версионировать. И точно так же документировать, точно так же тестировать на тестовой бд.

    Единственный значимый аргумент - это привязка к конкретной СУБД. И то каждый сам для себя определяет допустимую степень риска. Кому было нужно - потратили время и деньги и перешли с oracle/mssql. Значит, задача не является нерешаемой.

    Паническое нежелание работать с ХП иногда приводит к маразму. Настолько разработчики боятся малейшей логики в бд, что вместо select max(...), загружают список записей на клиента и уже там находят максимальное значение нужного поля (сам видел)


    1. rinace
      06.08.2024 10:23
      +1

      Настолько разработчики боятся малейшей логики в бд

      Я видеть такой кейс(регулярно повторяемый)- backend выгружает весь набор строк и фильтрует на стороне приложения.

      Закономерный итог -"а почему в psql запрос отработал миллисекунды а форма открывается минуту ?"


    1. Free_ze
      06.08.2024 10:23
      +2

      Настолько разработчики боятся малейшей логики в бд, что вместо select max(...), загружают список записей на клиента и уже там находят максимальное значение нужного поля

      Будь так, слали бы запрос с select max(...) и ХП не понадобилась бы. А тут больше похоже на то, что банально не подружились с генератором запросов в ORM.


    1. michael_v89
      06.08.2024 10:23

      Настолько разработчики боятся малейшей логики в бд, что вместо select max(...), загружают список записей на клиента

      Ну это конечно неправильно в определенных ситуациях, но в других ситуациях это может быть более подходящим решением. Без контекста нельзя точно сказать. Кроме неумения использовать ORM, как сказали выше, могут быть например такие причины:
      - Список записей потом используется для чего-то еще.
      - Для произвольного запроса надо писать 20 строк, а для вычисления в приложении 1. Тот, кто советует отправлять max() в базу, поддерживать их конечно не собирается. При этом строк в этой выборке не больше 10.
      - В коде проще отслеживать и менять логику средствами IDE, чем в строковых константах с SQL. Если разработчик приложения пропустит это место при изменениях требований, вы (DBA) будете ответственный или он? Вы будете исправлять этот баг в строковой константе? Зато 10 микросекунд сэкономили.


  1. sinelnikof88
    06.08.2024 10:23

    За большей частью текста полностью за !!! Работал 5 лет назад с оракловской бд, это дикая боль. За исключением как у них реализован рекурсивный обход, все остальное, жесть. Бизнес логика написана хранимками, и упиханна в пакеджи. Которые нужно ещё компилировать . Собирается все херово . Инстансы ещё нужно определенной версии. Но самая жопа это логика в базе!!!! База должна уметь репликации, дампить нормально, а не валится или битые с ходу делать. Я не хочу разбираться в сиквенсах, мне нужно надёжно данные сохранить . Уметь в кластер и давать АДЕКВАТНЫЕ ответы на ошибки. А не засирать диск тупыми логами , с нулевой информативностью . (Может в настройках дело, но это говно даже перезапустить боялись ) потому что она поднималась раза с 5 го. Логика - в код, данные в бд

    При каждой пересборке пакетов, отлетают клиенты. И есть шанс уронить вообще все.

    Согласен с автором логика в бд - за такое четвертовать !


  1. CrazyElf
    06.08.2024 10:23
    +6

    Прочитав статью можно подумать, что хранимые процедуры были придуманы какими-то идиотами, у которых просто нормального ORM-а не было. И что у хранимок нет никаких плюсов, а сугубо одни минусы. А между тем плюсы всё-таки есть.

    • Хранимые процедуры заранее компилируются и оптимизируются движком SQL. И поэтому теоретически работают быстрее, чем просто запросы к базе, делающие ту же работу, что и процедуры. Хотя этот плюс несколько нивелирован современными движками БД, которые кэшируют SQL запросы и их результаты, в том числе параметризуемые запросы.

    • Лучший контроль за транзакциями и исключениями на низком уровне.

    • Тонкие оптимизации, недоступные когда вы обращаетесь к БД через различные высокоуровневые прослойки, имеющие те или иные ограничения.

    Хотя я лично и сам ненавижу, когда логика размазана по слоям, и что там не очень то ООП получается на уровне БД когда программируешь. Но не упоминать, что у процедур есть и плюсы - это странно.


  1. Shura_m
    06.08.2024 10:23
    +5

    Как вы так оптом, легко и просто, умножаете на ноль целую технологию.

    У любой технологии есть свои «+» и «-», и для разных задач используют разные методики.

    Способ хранения данных, доступ к ним определяется на этапе проектирования системы.  Даже типы серверов (MS SQL,  Oracle, MySQL, ..) отличаются по функционалу, и все это нужно учитывать.

    Мне кажется, Ваше отношение к данной технологии вызвано неграмотно спроектированной системой на этапе бизнес логики, вызывающей такие проблемы. Должен быть уровень данных, предоставляющий данные в верхний уровень, и программист, пишущий, к примеру, на .NET, может совсем не знать что там внутри- SQL, XML или что-то совсем другое.

     

    Тут, как в анекдоте:

    -Вы не любите кошек? Да вы просто не умеете их готовить!


    1. michael_v89
      06.08.2024 10:23

      и программист, пишущий, к примеру, на .NET, может совсем не знать что там внутри- SQL, XML или что-то совсем другое.

      Одни говорят, что автор не знает и не разобрался, другие, что он и знать не должен)
      Кто будет писать в приложении этот "уровень данных, предоставляющий данные в верхний уровень" и потом поддерживать, вы (DBA) или разработчик приложения? Зачем ему эти сложности?


  1. StanEgo
    06.08.2024 10:23
    +6

    Хранимые процедуры замечательно тестируются, отлично оптимизируются. В задачах, для которых они предназначены, нет ни одного ЯВУ, который был бы более выразителен. Добавляют ещё один уровень безопасности, полностью скрывая реальные структуры данных, полиморфны, можно легко добавить трейсинг, профайлинг и т.п., прекрасно мигрируют на другую СУБД даже без ребилда и деплоя приложений, можно без труда реализовать поддержку множества СУБД, классно помогают в скейлинге/миграции схемы когда нужно осуществить плавную трансформацию в фоне петабайтов данных и временно спрятать её за вызовом хранимки. У нас замечательно и из Linq генерируются хранимки. А вот за что точно надо отрывать руки - это когда противники хранимок гоняют гигабайты данных туда-сюда, пропуская их через лес уродливого кода, для работы в таком ключе вообще не приспособленного))


    1. rinace
      06.08.2024 10:23

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

      "В детстве , я таких убивал. Из рогатки".(с)


    1. NikolayTheSquid
      06.08.2024 10:23
      +2

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


  1. ptr128
    06.08.2024 10:23

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

    Или все же "кесарю кесарево"? Может "золотого молотка" не бывает и любой инструмент имеет свою область применения?


    1. GlukKazan
      06.08.2024 10:23

      Уже. Функции - это тоже хранимки.


      1. ptr128
        06.08.2024 10:23

        Ага, и если нужна кастомная агрегатная функция, то ни-ни - тащи все строки на клиента и там агрегируй?


  1. N4N
    06.08.2024 10:23

    Все описанные минусы следуют из принятой как стандарт архитектуры, подразумевающий разделение приложения на слои. Слой данных в ней рассматривает СУБД как просто место хранения табличных данных. Но даже при таком подходе обычные crud операции можно реализовать как ХП, и вызывать их, просто передавая параметры в ХП и, если нужно, получать результат. Это будет гарантировано быстрее, чём держать SQL код в коде другого языка и который будет компилироваться сервером бд каждый раз при его выполнении. Плюс выглядит это ужасно, каждый раз когда смотрю какой-нибудь @Repository с query native=true, испытываю неприятные ощущения. Какой уж тут clear code, когда на пол экрана висит строка с жутким SQL )

    И аргумент, что SQL код начинает зависеть от конкретной реализации сервера, довольно слабый. Кто-то может привести пример, когда постоянно нужно менять сервер? На моей очень большой практике разработки такое было только два раза. Один раз, когда заказчик сразу сказал, что у него Oracle, и MS SQL ему не нужен, перепишите все. И второй когда вот это вот все началось и все ринулись импортозамещать все что можно, и решение нужно было перевести на postgres. Но оно уже было на orm и переход проходил довольно гладко


    1. michael_v89
      06.08.2024 10:23

      Но даже при таком подходе обычные crud операции можно реализовать как ХП, и вызывать их

      Можно. Только нафига?

      Это будет гарантировано быстрее

      Насколько быстрее? Через сколько часов работы приложения окупится дополнительный час работы программиста?


  1. S_gray
    06.08.2024 10:23
    +3

    Да ответ-то на всё простой - дизайнить систему надо правильно: бизнесу - бизнесово, базе - базово. Тогда всё уляжется. Отцы-основатели ООД и ООА свое дело туго знали, не то что нынешнее племя...


  1. Batalmv
    06.08.2024 10:23
    +1

    Я бы сказал так.

    Для юзер-ориентированных приложений, построненный по трех-уровневой архитектуре - хранимки "это рудимент". В новых приложениях использовать такие решения крайне плохо и никакая "лапша на уши" про 2% выигрыша производительности того не стоит.

    Одно размазывание логики чего стоит, плюс полная невозможность переиспользования реализации в разных слоях.

    ------------

    Но есть куча задач в самой БД либо для переливки\подготовки данных. Тут хранимки - это хорошо и правильно.

    ----------

    Немного по пунктам

    Сложность в тестировании

    Для юнит тестов - наверное да, так как надо "готовить" кучу данных, что для логики в приложении как правильно не проблема (ну или как минимум легче подкинуть в тест данные). Для чего-то интеграционного данные так или иначе надо готовить, поэтому тут уже не факт, что хранимки чего-то там усложняют

    Автоматическое тестирование или unit-тесты

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

    Например, от бизнес-партнеров поступило требование собирать метрики по определённым участкам бизнес-процесса для их оценки. И как мы будем это делать? Правильно, реализовывать ещё больше костылей, потому что SQL — это язык для агрегации данных, а не язык для описания бизнес-логики приложения. В любом современном языке программирования достаточно подключить какую-либо библиотеку и настроить URL /metrics для сбора необходимых метрик

    И да, и нет. Зависит от метрик. Ну и надо понимать, что сбор метрик в интеграционной среде часть это много разных приложений, что уже не так сильно зависит от конкретных технологий реализации


  1. duke_alba
    06.08.2024 10:23

    Любая категоричность а подобных вопросах - вещь сильно спорная. Вот IBM в своё время чуть не разорилась из-за злоупотребления оператором goto, и потом целое поколение программистов шарахалось от этого оператора, как чёрт от ладана. Многие даже и не знали первопричины. Просто - детская травма :-)

    Второй пример из области БД: триггеры - это зло! Теперь - хранимые процедуры, как оказалось, тоже зло.

    Возможно, просто надо разобраться, что для чего использовать, и в каких количествах?