Комментарии 45
От себя, я бы подумал использовать systimestamp вместо sysdate.
Штука полезная, благодаря ей в свое время благодаря этому находили странные ошибки
Все так! Очень приятно, что разработка на PL/SQL не умерла!
Думаете это хорошо? Я пол жизни хотел поработать с ораклом, поработал, не понравилось, в том числе изза огромного легаси которое по репозиторий даже не слышало. Классный способ выстрелить себе в ногу.
Кроме того видел очень много ораклистов, которые так заигрались в pl/sql что даже вещи которые можно сделать проще (например check) заворачивают тудаже.
А уж в какую прелесть превращается переиспользование кода, мммм… Функции вызываемые построчно, функции в условии, каскад процедур функий, ням ням.
И код пере использовали и делали апи для вызова заказчиками. Много чего было сделано и работает очень хорошо.
А так, нет там сложности с интеграцией.
PL/SQL Developer и Oracle SQL Developer умеют работать с git. В конце концов можно и файлы сохранять.
Для дистрибутива лучше использовать ликвид.
Не, норм все. Весь вопрос в процессе
Ликвид конечно вариант, но весьма костыльный.
Скажу по секрету что гитора взламывается на раз два(потому что анвраппер). И при этом можно активировать типа платные вип-фичи для понтового просмотрщика репозитория. Но только тсс))) И там еще и свои болячки тоже есть у него принципиальные, по которому оно для прода не готово вообще. Побаловаться в тестовых средах еще может быть...
Используем plog для не очень важных/нагруженых мест и где не так сильно много логов надо писать.
Потому что логи в таблицу — это фу.
Используем plog для не очень важных/нагруженых мест и где не так сильно много логов
Я об этой ситуации написал в статье:
Также, часто в командах появляются разработчики, которые говорят: «Зачем логировать все процедуры (функции)? Давайте вести логирование только важных и нужных процедур (функций)!».
У всех разработчиков разное понимание «не очень важных/нагруженых мест» и если использовать такой критерий логирования, то тогда наверное такое логирование будет бесполезным.
Используем plog ...
Это все хорошо до тех пор, пока вы не попадете в крупную ИТ-компанию, например Сбер, ВТБ. На коммерческий продукт для логирования ошибок в Oracle в проекте никто никогда не закладывает бюджет, а использовать оперсоурсные по, библиотеки и прочее вам скорее всего не дадут «безопасники».
Потому что логи в таблицу — это фу
По своему опыту использования таблиц для логов, могу сказать, что в среднем в партицию за один квартал прилетает примерно 5-6 тыс строк. В период активного внедрения нового функционала кол-во ошибок возрастает, но со временем цифра усредняется. Зачем изобретать велосипед и если не в Oracle, то тогда где вести лог ошибок? В файлах на удаленном сервере? И ведь такой «лог» ошибок практически бесполезен, я не говорю о том, что рано или поздно файлы лога забьют свободное место на дисках. Контролировать наполнение таблицы лога вы всегда можете своими силами (силами отдела разработки или сопровождения), а вот файловый сервер это как правило зона ответственности администраторов. И по своему опыту в таком банке могу сказать, что такой лог ошибок часто «ронял» сервер.
partition by range (insertdate)
Это подразумевает не только использование Enterprise Edition, но ещё и «Extra cost option». Значит, без нарушения лицензионного соглашения, Ваш метод не подойдёт для других редакций.
Во первых, я бы рекомендовал захинтовать вставку при помощи APPEND NOLOGGING, чтоб ускорить и не генерить redo для этих данных. А ещё покурите асинхронный коммит, например https://www.orafaq.com/node/93. Я вижу у вас партиции, следовательно это решение предназначено для прода а не для разработки.
А не лучше ли в таком случае вместо хранения логов в базе слать их во внешнюю систему? Можно написать простейший Java source пакетик (или или готовый байткод загрузить в базу), и через него стримить наши логи в Apache Kafka или в любую другую шину? Оттуда направлять это куда душе угодно, в любую time-series database, или в старый добрый Prometheus, потом их можно будет видеть в системе мониторинга вкупе с остальными метриками и делать выводы, например в Grafana Loki. Заодно и retention policy для логов проще управлять. И можно алерты настроить чтоб по мейлу или прям в телегу получать важные оповещения.
И в целом, мне кажется вы чуть-чуть преувеличиваете важность таблицы логирования событий. Чтоб вы понимали, за весь вчерашний день 31.03.2021 в таблице лога на прод сервере у нас появилось 124 строчки (3 с типом Err и 121 с типом Msg).
По поводу хранения логов во внешней среде, то я уже видел подобные реализации. Во-первых, основная проблема это в дальнейшем использовании этих логов (неудобно анализировать их, искать конкретную ошибку и т.д.). Во-вторых, файлы периодически заполняют все свободное место.
В дальнейших статьях я постараюсь описать как мы настроили мониторинг событий в самом Oracle. Мы в текущей компании используем qlikview для визуализации событий.
Я вижу у вас партиции, следовательно это решение предназначено для прода а не для разработки.
Вот здесь если честно я не понял о чем речь. У нас на всех стендах от развернуто подобное логирование. На данный момент могу сказать, что им пользуются разработчики на dev-стенде, тестировщики на своем (прелайв) стенде и пользователи используют лог на прод-сервере.
Но текущая реальность несколько иная.
У нагруженных систем, логов обычно не 100 или 1000 в день, а от десятков миллионов.
В свете этого всякие ES, CH и прочие, не зря свой хлеб едят.
Традиционные БД итак, обычно, узкое место, еще и логи писать потоком.
При этом и выбор в plsql не очень большой. Либо в табличку писать, либо по сети через java pkg. Из табличек, понятно, все равно потом нужно данные переливать в ES, CH. Можно, конечно, работу с табличками оптимизировать — batch, partition, no index, multiple tables, etc и дожать до 4-10 тыс с сек на хорошем железе. Но если будет больше, придется съезжать на другой стек.
нагруженных систем, логов обычно не 100 или 1000 в день, а от десятков миллионов.
Согласен, наверное в случае миллионов логов в день, то лучше использовать промышленное решение. В одной компании (в которой я работал раньше и где уже был реализовано подобное логирование) с течением времени перешли на коммерческое решение для логирования всего массива событий. Такой качественный переход был осуществлен только тогда, когда появилось понимание, что вышеописанного логирования недостаточно чтобы покрыть нужды компании.
Поэтому я в описании статьи делаю упор на то, что подобное логирование нужно создавать именно на «начальных» этапах построения БД.
Обработка when others без raise внутри — отличный способ выстрелить себе в ногу. Вызванная процедура отработала вхолостую, вызывающая об этом ничего не знает и продолжает как ни в чем не бывало.
Архитекторы у вас боятся пустых полей, зато не боятся лишнего insert. Очень странные архитекторы.
А ещё rollback без savepoint, который откатывает весь dml, сделанный в вызывающей процедуре. Такая мина заложена, что когда она взорвется, уйдет не один час на то, чтобы понять, почему данные не сохраняются в базу.
Вероятность 50/50, что программист при добавлении параметра в процедуру забудет добавить его в строку p_paramvalue и в самый неподходящий момент выяснится, что его значение не залогировано, а оно-то как раз и необходимо.
Хороший набор антипаттернов для включения в каждую процедуру!
Вероятность 50/50, что программист при добавлении параметра в процедуру забудет добавить его в строку p_paramvalue и в самый неподходящий момент выяснится, что его значение не залогировано, а оно-то как раз и необходимо.
Да, есть такая вероятность и даже сам с этим сталкивался. А какая может быть альтернатива? Пока что остается одно — вручную вносить список параметров в p_paramvalue.
Самая сложная категория багов — это баги, допущенные в обработчике исключительных ситуаций. И обработчик из вашего примера их содержит.
Список параметров каждой процедуры есть в системных view. Можно написать функцию проверки наличия всех параметров в тексте пакета/процедуры и запускать эту функцию по триггеру в момент компиляции пакета/процедуры на тестовом или разработческом стенде.
Тогда программист не сможет забыть указать параметр.
Мое мнение такое, что when others then… raise и rollback to savepoint в обработчике ошибок общего назначения — это как раз должно использоваться всегда, а вот отказ от них возможен в редких частных случаях.
За свою практику работы с Oracle я наблюдаю диаметрально противоположную картину — во многих компаниях используется концепция: «если транзакция падает с ошибкой (типа «when others then»), то данную транзакцию завершают полным откатом изменений т.е. либо алгоритм отрабатывает без ошибок и сохраняем результат, либо завершаем алгоритм и не сохраняем вообще ничего».
Такая практика поможет избежать множества сложновылавливаемых ошибок.
Я бы сказал, что все наоборот. Такая практика только усложняет понимание корректности полученных данных. Поясню на примере, у вас есть функция которая рассчитывает процент по кредиту для клиента. Внутри этой функции вызовы множества процедур, которые рассчитывают различные атрибуты, параметры которые необходимы при расчете итогового процента по кредиту. Но вот в одной из процедур возникла неизвестная «when others then» ошибка и что делать тогда? В нашем случае мы уроним весь расчет и залогируем ошибку с параметрами запуска процедуры в которой произошла ошибка. После того, как исправят данную ошибку, то можно будет перезапустить расчет процента для указанного клиента. В вашем же случае, произойдет откат до сохраненной точки, а дальше что? Вы продолжите рассчитывать остальные атрибуты и на основании их посчитаете процент по кредиту? Т.е. у вас есть некий итог работы функции, но вот как понять что он корректный?
Это я все к тому, что способы написания кода бывают разные, у вас свои взгляды и нас свои. Вопросы rollback, raise и savepoint я в статьях не затрагиваю т.к. смысл статей не в этом. Еще раз раз повторюсь, я лишь показываю как можно отловить ошибку с её параметрами в момент её возникновения. Что вы (либо другой читатель) будете делать после возникновения ошибки это дело непосредственно ваше.
Список параметров каждой процедуры есть в системных view. Можно написать функцию проверки наличия всех параметров в тексте пакета/процедуры и запускать эту функцию по триггеру в момент компиляции пакета/процедуры на тестовом или разработческом стенде.
Если я так сделаю на каком-либо проекте, то меня будут вспоминать проклиная и ненавидя… На самом деле это не такая частая проблема, да иногда забывают и ничего не мешает добавить позже, все решается обычным кодревью.
«если транзакция падает с ошибкой (типа «when others then»), то данную транзакцию завершают полным откатом изменений т.е. либо алгоритм отрабатывает без ошибок и сохраняем результат, либо завершаем алгоритм и не сохраняем вообще ничего».
Так как раз ваш предлагаемый подход в КАЖДОЙ процедуре глушить все иск.ситуации и делать rollback противоречит этой здравой концепции.
Предположим у нас есть управляющая процедура Main (в ней одна транзакция, в конце процедуры commit), которая вызывает по очереди три процедуры, которые делают какие-то этапы общей транзакции, выполняют dml. Назовем их Step1, Step2, Step3. Если в Step2 произойдет exception, то произойдет откат dml, выполненных в Step1 и Step2. Далее, поскольку raise не сделан, управление перейдет в Step3, и затем общий commit в Main. В итоге мы получили в базе не «все или ничего», а только dml из Step3, что при разборе крайне загадочно и поставит разработчика в тупик.
Если же использовать rollback to savepoint Step2 и raise, управляющая процедура получит информацию, что Step2 не выполнен и сможет далее принять решение, можно ли переходить к Step3 или нужно аварийно завершиться без commit. Если в Main программист не предусмотрел блок обработки ошибок, то после exception в Step2 Main завершится аварийно без commit и мы получим «все или ничего».
Куда логировать, в таблицы или во внешний инструмент, как потом мониторить, это уже детали. Суть в правиле — каждый exception должен логироваться в той процедуре, в которой он произошел. И типовой обработчик — это не просто пример, а важнейшая часть этой концепции, которую нужно один раз написать как шаблон, а потом постоянно вставлять в код всем разработчикам. Ошибки в этом шаблоне, раскопированные по всему коду, могут просто похоронить проект.
Также, прошу обратить внимание на выходные параметры процедуры-шаблона
p_errcode out number
p_errtext out varchar2
обычно на них идет условие дальнейшего выполнения алгоритма, либо его завершения, но в примерах текущей статьи я их не использовал.
Но все равно недостаточно хорошо, потому что там не инициализируются p_errcode и p_errtext и при этом нет raise. Таким образом вызывающая процедура не знает о том, что в данной процедуре произошла непредвиденная ситуация. Что приведет к сложновылавливаемым ошибкам.
Ну и в принципе под when others подпадает и ошибка отсутствия нужной партиции для вставки записи, и ora-600 и много других непредвиденных системных сбоев, которые должны, просто обязаны останавливать исполнение всей последовательности вызовов и „орать“ в мониторинг о срочном вмешательстве поддержки. Поэтому в when others должен быть raise.
Если я так сделаю на каком-либо проекте, то меня будут вспоминать проклиная и ненавидя…Почему? Например, компиляцию нельзя выполнить, если в коде есть синтаксическая ошибка. Это же не расстраивает программиста, а наоборот, помогает.
Если у вас на проекте имеются требования к оформлению кода, то автоматический контроль соблюдения этих требований упростит жизнь программистам. За это они спасибо скажут. Главное делать этот контроль оптимально, чтобы только реальные проблемы не допускал, а не вставлял палки в колеса.
На самом деле это не такая частая проблема, да иногда забывают и ничего не мешает добавить позже, все решается обычным кодревью.
Я часто делаю код ревью. При этом предлагаете пересчитывать на глаз количество параметров в заголовке процедуры и в ее хвосте, которые могут быть за сотни строк друг от друга? А параметров может быть штук 10. Или так ПО КАЖДОЙ процедуре? И на это будет тратить свое время самый ценный трудовой ресурс команды (обычно ревью делает тимлид)?
Почему? Например, компиляцию нельзя выполнить, если в коде есть синтаксическая ошибка. Это же не расстраивает программиста, а наоборот, помогает.
Мой опыт работы в команде разработчиков говорит мне только о том, что большинство разработчиков (к сожалению) только пишет код, не думая о том как с этим кодом будут работать другие в будущем. Заставлять людей делать что-то «принудительно» не работает, либо работает до тех пор пока есть кому требовать и контролировать выполнение требований. Да, наверное так можно сделать, но я такой метод использовать не стал бы.
Я часто делаю код ревью. При этом предлагаете пересчитывать на глаз количество параметров в заголовке процедуры и в ее хвосте, которые могут быть за сотни строк друг от друга? А параметров может быть штук 10. Или так ПО КАЖДОЙ процедуре?
Да, в каждой процедуре вручную вставлять параметр и это удобно делать когда у вас есть контроль версий (Tortoise, git и прочее). Со временем уже на автомате видишь новый параметр и ниже смотришь его в блоке исключений.
Если вы никогда не использовали данный метод это не значит, что он не работоспособный.
Мы используем Logger, который рекомендовал Том Кайт. Единственное что, мы секционировали таблицу по дням, и чистим с помощью дропа партиций, а не с помощью удаления строк. То есть мы сделали изменения относительно оригинала, но они минимальные.
Из плюсов:
эта штука поддерживает переключение, например, с уровня info на уровень debug на лету;
при использовании log_error в эксепшен-блоке, в лог автоматом вставится стек трейс, то есть нам достаточно только словами описать, что у нас не получилось сделать, а остальное за нас сделает логгер;
если мы в начале процедуры положили входные параметры в специальную коллекцию, то в случае log_error мы их тоже увидим.
Но главный плюс, конечно, в том, что мы не стали тратить время на написание собственного логгера на pl/sql.
Опять же, все зависит от команды и отношения к логированию. Просто в данной статье я показал один из способов логирования.
Также, наверное необходимо эту статью рассматривать совместно со второй (третьей и четвертой которые в разработке) частью.
«Все объекты базы данных (функции, процедуры) в обязательном порядке должны завершаться блоком обработки исключений с последующим логированием события».
А почему обязательно все? Почему не ловить exception только в процедурах верхнего уровня, которые вызываются пользовательской сессии или из джоба?
Ну или хотя бы только в процедурах, которые есть в заголовке пакета, и потенциально могут быть вызваны пользовательской сессии или из джоба?
Моя позиция такая, что проще вставить кусок кода в блок exception в момент написания процедуры один раз и навсегда, нежели его не писать или рассуждать о том нужен он или нет. И суть данного метода логирования ошибок состоит в том, чтобы отловить максимально те параметры на которых возникла ошибка, чтобы в дальнейшем использовать их для воспроизведения ошибки на тестовом стенде и быстрого исправления. Но тут уже выбор каждой команды как им вести или вообще не вести логирование ошибок.
Часть 1. Логирование событий в Oracle PL/SQL