2003-04-14 08:28:20 UTC
В процессе адаптации этого сайта, точнее его backend части, для СУБД PostgreSQL, я натолкнулся на один очень интересный факт, даже можно сказать подводный камень, который многому меня научил.
Проблема возникла из-за, вообщем-то очень нужной и полезной вещи, которую PostgreSQL, в отличие от MySQL, поддерживает по-умолчанию — механизма транзакций. Оказывается, что пачка SQL запросов (или один запрос) в рамках одного соединения парсера с данной СУБД для любой из страниц сайта, выполняются в одной транзакции. Известно, что транзакция это либо пан, либо пропал, т.е. транцакция считается успешной если все без исключения её запросы безошибочны. Иначе ни один из этих запросов не будет выполнен, если хотя бы один из них дает сбой, и механизм транзакций вернёт БД в прежнее состояние (если запросы изменяли состояие БД).
Теперь пробуем угадать, — в чем же подвох? А подвох в том, что при наличии механизма транзакций, нельзя использовать механизм обработки исключений как ещё одну управляющую структуру, если в методе вызывающем исключение, производится SQL запрос. Рассмотрим это подробнее.
Обработка исключений в любом языке может использоваться не только по своему прямому назначению — для обработки ошибок, но и как ещё одна управляющая структура, наряду с условными операторами, операторами цикла и т.д. Допустим у нас есть метод @method[...]
имеющий некие входные параметры и работающий только для строго определённого диапазона значений входных параметров. Если значения входных параметров находятся вне нужного диапазона, метод не работает. Мы естественно хотим, чтобы метод работал всегда, даже когда ему передаются некорректные параметры.
Можно по разному подходить к решению этой проблемы. Первый способ — явно проверять входные параметры на допустимость (или внутри метода или в месте его вызова) и если они недопустимые, присваивать им некие значения по-умолчанию, и начинать работу метода. А можно использовать механизм обработки исключений, например:
^try{ ^method[$param1;$param2] }{ ^if($exception.type eq some.type){ $exception.handled(1) $param1[default_value] $param2[default_value] ^method[$param1;$param2] } }
Здесь мы нигде не проверяем входные параметы на допустимость, а просто вызываем метод в блоке ^try
(поскольку он может вызывать исключения). При допустимых входных параметрах всё идёт хорошо и никаких ошибок нет. При недопустимых метод генерирует исключение определённого типа, которое мы обрабатываем в обработчике ( устанавливаем флаг $exception.handled(1)
), присваиваем входным параметрам допустимые значения и вызываем метод снова (но уже с допустимыми значениями). Часто такой способ гораздо элегантнее и проще принудительной проверки допустимости входных значений, но тут могут быть подводные камни.
На один из них я и напоролся. Как было сказано выше, в этом методе, вызывающем исключения, есть SQL запрос к БД, который собственно эти исключения и вызывает. Понятно, что в случае вызова метода с некоректными параметрами (в моем случае со значениями NULL), вызов этого метода производится дважды: 1-й раз он не работает (происходит ошибка синтаксиса в SQL запросе), 2-й раз все нормально. В случае с MySQL, в которой по-умолчанию, отсутствуют транзакции, всё хорошо, — сбой в одном из запросов, не приводит к невыполнению других запросов. В случае с PostgreSQL (а возможно и в других СУБД с поддержкой транзакций), сбой в одном из запросов, приводит к невыполнению всех запросов соединения (страницы), что мне было совсем не нужно. Более того, при выводе сообщения об ошибке, на экран выводился последний запрос транзакции, в котором не было никакой ошибки. Эту нетривиальную ошибку удалось выловить только с помощью анализа журнала СУБД.
Резюмируя сказанное, стоит запомнить:
- Использование обработки исключений как ещё одной управляющей конструкции, может приводить к нетривиальным ошибкам и пользоваться этим нужно с умом и с осторожностью.
- Поиск ошибок при работе с PostgreSQL нужно производить с использованием профайлера или журнала СУБД — на экран в режиме отладки не всегда может выводиться ошибочный запрос.