Вызов исключений и обработка ошибок с помощью THROW в SQL Server
Пересказ статьи Jared Westover. Raising Exceptions and Error Handling with SQL Server THROW
Рассматривали ли вы возможность добавления обработки ошибок в код Transact-SQL (T-SQL)? Если вы спросите опытных разработчиков, большинство из них согласится с тем, что это хорошая идея. Возможно, вам достался по наследству далеко не идеальный код. Или ваш код можно было бы немного привести в порядок. Основной причиной для добавления обработки ошибок является управление возникновением исключений. Было бы прекрасно, если бы ошибки не возникали, но такой мир не существует. Есть пара способов для вызова исключений в T-SQL. Более старый метод - это с использованием RAISERROR. Теперь RAISERROR все еще используется, но, начиная с SQL Server 2012 в городе появился новый игрок, которого зовут THROW.
Здесь мы рассмотрим использование THROW. Я укажу на некоторые преимущества и недостатки. В итоге вы сможете принять обоснованное решение о выборе способа реализации для ваших данных на SQL Server.
Обычно вы встречаете THROW внутри блока TRY...CATCH. Однако вы можете использовать THROW с параметрами отдельно. Давайте рассмотрим простой пример. Вы можете выполнять все приведенные здесь скрипты в SQL Server Management Studio (SSMS) без установки базы данных SQL Server.
Для работы вышеприведенного примера мы должны указать ID сообщения, текст и состояние. Что хорошо в случае THROW, не требуется, чтобы ID сообщения имелось в sys.messages. Кажется, я всегда использую 1 для состояния.
Как упоминалось выше, вы обычно используете THROW в сочетании с блоком TRY...CATCH, как показано в следующем примере.
Замечательной особенностью THROW при использовании внутри TRY...CATCH является то, что вам нужно всего лишь напечатать THROW. Если бы существовала простая кнопка для вызова исключения, то это была бы она.
Давайте потратим минутку и рассмотрим самые известные преимущества THROW, особенно в сравнении с RAISERROR. Сразу скажу, что простота THROW не имеет себе равных. Тем не менее, вот несколько других замечательных особенностей.
Посмотрите снова на скриншот выше. Вы получаете реальное сообщение, возвращаемое, если просто напечатать THROW. Вам не нужно беспокоиться об использовании функций обработки ошибок. Обратите внимание также на номер строки. Это номер строки, где произошло исключение, а не там, где мы вызывали THROW в блоке кода. Вообразите себе отладку кода в 1000+ строк. Получение точного номера критично при поиске возникающих ошибок.
Если вы используете THROW вне блока TRY...CATCH, вам не нужно беспокоиться о наличии ID сообщения. Вы можете использовать любой старый номер. При использовании RAISERROR вам нужно было добавить сообщение в sys.messages, чтобы это работало.
Когда я встречаю исключение, мне обычно хочется откатить транзакцию (ROLLBACK) и прервать выполнение пакета. Когда вы выполняете THROW, пакет останавливается, и SQL Server не выполняет никаких последующих операторов. Обратите внимание, что в примере ниже PRINT не выполняется. Вот синтаксис:
Наконец, начиная с SQL Server 2012, Microsoft рекомендовало использование THROW. Конечно, это, в первую очередь, зависит от того, зачем вы используете THROW. Я все еще использую RAISERROR в специальных случаях, что вы увидите в следующем разделе.
Давайте потратим немного времени на рассмотрение некоторых недостатков THROW. Вот некоторые, которые обычно выделяют разработчики.
Один из основных недостатков THROW - это невозможность вызова информационных сообщений. С RAISERROR вы можете использовать более низкие уровни серьезности. Например, уровень серьезности 10 возвращает информационное сообщение пользователю. Этот единственный недостаток оставляет RAISERROR в игре. Обратите внимание на черный текст на скриншоте ниже.
Я упоминал прерывание пакета в списке преимуществ, но это также и недостаток. Если вы хотите продолжить после возникновения исключения, для вас это недостаток. Такое поведение является еще одной причиной, почему я временами все еще использую RAISERROR. Говоря о THROW, я иногда сравниваю его с футбольной игрой, когда судья бросает флаг на поле. Когда флаг брошен, игра останавливается.
С RAISERROR вы можете выбрать запись сообщений в журнал ошибок SQL Server. Вы можете также вызвать исключение прежде, чем завершится весь пакет. THROW не предлагает ничего подобного. Я редко использую эти опции, поэтому это не тревожит меня.
Некоторые люди зацикливаются на необходимости завершить предыдущий оператор перед THROW. Для меня это не большая проблема, но позвольте привести пример. Если вы выполните нижеприведенный оператор, вы получите сообщение об ошибке при выполнении CATCH.
Чтобы обойти это, добавьте точку с запятой после ROLLBACK TRANSACTION или перед THROW. Я использовал ограничители операторов, по крайней мере, 10 лет, и советую другим делать то же самое.
Фильм «Горец» был одним из лучших фильмов 80-х. Его слоганом было: «Должен остаться только один». В отличие от кино, вы не должны выбрать одно из. Вы можете использовать код ниже, если вы ищите ванильный метод для вызова исключений. Можете скопировать и вставить его в свой шаблон.
Однако из-за упомянутых выше недостатков не забывайте о RAISERROR. Ваш выбор зависит от цели. Для меня было бы много легче сказать, чтобы вы всегда делали то или это, но, как и для большинство вещей в SQL Server, ответ - "это зависит от...".
Использование оператора THROW в SQL Server
Обычно вы встречаете THROW внутри блока TRY...CATCH. Однако вы можете использовать THROW с параметрами отдельно. Давайте рассмотрим простой пример. Вы можете выполнять все приведенные здесь скрипты в SQL Server Management Studio (SSMS) без установки базы данных SQL Server.
THROW 50000, 'Houston, we have a problem.', 1;
Для работы вышеприведенного примера мы должны указать ID сообщения, текст и состояние. Что хорошо в случае THROW, не требуется, чтобы ID сообщения имелось в sys.messages. Кажется, я всегда использую 1 для состояния.
Как упоминалось выше, вы обычно используете THROW в сочетании с блоком TRY...CATCH, как показано в следующем примере.
BEGIN TRY -- Блок TRY
SELECT 1 / 0; -- Оператор SELECT
END TRY
BEGIN CATCH -- Блок CATCH
THROW;
END CATCH;
Замечательной особенностью THROW при использовании внутри TRY...CATCH является то, что вам нужно всего лишь напечатать THROW. Если бы существовала простая кнопка для вызова исключения, то это была бы она.
Преимущества THROW
Давайте потратим минутку и рассмотрим самые известные преимущества THROW, особенно в сравнении с RAISERROR. Сразу скажу, что простота THROW не имеет себе равных. Тем не менее, вот несколько других замечательных особенностей.
Сообщение об ошибке и номер строки
Посмотрите снова на скриншот выше. Вы получаете реальное сообщение, возвращаемое, если просто напечатать THROW. Вам не нужно беспокоиться об использовании функций обработки ошибок. Обратите внимание также на номер строки. Это номер строки, где произошло исключение, а не там, где мы вызывали THROW в блоке кода. Вообразите себе отладку кода в 1000+ строк. Получение точного номера критично при поиске возникающих ошибок.
ID сообщения
Если вы используете THROW вне блока TRY...CATCH, вам не нужно беспокоиться о наличии ID сообщения. Вы можете использовать любой старый номер. При использовании RAISERROR вам нужно было добавить сообщение в sys.messages, чтобы это работало.
Прерывание пакета
Когда я встречаю исключение, мне обычно хочется откатить транзакцию (ROLLBACK) и прервать выполнение пакета. Когда вы выполняете THROW, пакет останавливается, и SQL Server не выполняет никаких последующих операторов. Обратите внимание, что в примере ниже PRINT не выполняется. Вот синтаксис:
BEGIN TRY
SELECT 1 / 0;
END TRY
BEGIN CATCH
THROW;
PRINT 'Я очень надеюсь, что это сработает!';
END CATCH;
Рекомендации Microsoft
Наконец, начиная с SQL Server 2012, Microsoft рекомендовало использование THROW. Конечно, это, в первую очередь, зависит от того, зачем вы используете THROW. Я все еще использую RAISERROR в специальных случаях, что вы увидите в следующем разделе.
Недостатки THROW
Давайте потратим немного времени на рассмотрение некоторых недостатков THROW. Вот некоторые, которые обычно выделяют разработчики.
Информационные сообщения
Один из основных недостатков THROW - это невозможность вызова информационных сообщений. С RAISERROR вы можете использовать более низкие уровни серьезности. Например, уровень серьезности 10 возвращает информационное сообщение пользователю. Этот единственный недостаток оставляет RAISERROR в игре. Обратите внимание на черный текст на скриншоте ниже.
RAISERROR ('Now you know. And knowing is half the battle.',10,1)
Прерывание пакета
Я упоминал прерывание пакета в списке преимуществ, но это также и недостаток. Если вы хотите продолжить после возникновения исключения, для вас это недостаток. Такое поведение является еще одной причиной, почему я временами все еще использую RAISERROR. Говоря о THROW, я иногда сравниваю его с футбольной игрой, когда судья бросает флаг на поле. Когда флаг брошен, игра останавливается.
Отсутствие NOWAIT и WITHLOG
С RAISERROR вы можете выбрать запись сообщений в журнал ошибок SQL Server. Вы можете также вызвать исключение прежде, чем завершится весь пакет. THROW не предлагает ничего подобного. Я редко использую эти опции, поэтому это не тревожит меня.
Завершение оператора
Некоторые люди зацикливаются на необходимости завершить предыдущий оператор перед THROW. Для меня это не большая проблема, но позвольте привести пример. Если вы выполните нижеприведенный оператор, вы получите сообщение об ошибке при выполнении CATCH.
BEGIN TRY
BEGIN TRANSACTION;
SELECT 1 / 0;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
ROLLBACK TRANSACTION
THROW;
END CATCH;
Чтобы обойти это, добавьте точку с запятой после ROLLBACK TRANSACTION или перед THROW. Я использовал ограничители операторов, по крайней мере, 10 лет, и советую другим делать то же самое.
Что использовать?
Фильм «Горец» был одним из лучших фильмов 80-х. Его слоганом было: «Должен остаться только один». В отличие от кино, вы не должны выбрать одно из. Вы можете использовать код ниже, если вы ищите ванильный метод для вызова исключений. Можете скопировать и вставить его в свой шаблон.
BEGIN TRY
BEGIN TRANSACTION;
-- Что-то делаем важное;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
ROLLBACK TRANSACTION;
THROW;
END CATCH;
Однако из-за упомянутых выше недостатков не забывайте о RAISERROR. Ваш выбор зависит от цели. Для меня было бы много легче сказать, чтобы вы всегда делали то или это, но, как и для большинство вещей в SQL Server, ответ - "это зависит от...".
Обратные ссылки
Автор не разрешил комментировать эту запись
Комментарии
Показывать комментарии Как список | Древовидной структурой