Что произойдет при попытке выполнить некорректно сформированный sql запрос java

Что произойдет при попытке выполнить некорректно сформированный sql запрос java

В предыдущей статье мы только познакомились с JDBC и написали простое приложение, которое позволило нам соединиться с СУБД и получить данные с помощью SQL-запроса. Хоть программа и не очень сложная, но на мой взгляд, мы сделали весьма важный шаг — мы смогли соединиться с базой данных и сделать пусть и простой, но запрос. Все, что мы рассмотрим дальше — это уже более удобные и более профессиональные способы использования JDBC.

Запросы на получение данных и запросы на обновление

SQL-запросы можно условно разделить на две группы:

Для первой группы используется уже знакомый нам метод интерфейса StatementexecuteQuery(). В принципе для начала этого метода вполне достаточно. Он покрывает очень большой процент запросов, которые разрабатываются для реальных систем. Позже мы познакомимся с дополнительными возможностями, но на данных момент советую запомнить — если надо получить данные из таблицы, то executeQuery в подавляющем большинстве случаев будет самым правильным выбором.
Для второй группа запросов (опять же в большинстве случаев) может использоваться другой метод интерфейса StatementexecuteUpdate(). Есл посмотреть документацию, то в отличии от executeQuery() (который возвращает ResultSet) этот метод возвращает целое число, которое говорит сколько строк в таблице было изменено при исполнении вашего запроса.
Например, вы можете оператором DELETE FROM JC_CONTACT удалить ВСЕ строки (посему будьте очень аккуратны). В этом случае метод executeUpdate() вернет количество удаленных строк. В некоторых ситуациях знание о количестве измененных строк бывает удобным для построения алгоритмов работы с данными.
В принципе с этим вопросов можно закончить — главное мы уже увидели. Для выборки данных — executeQuery(). Для изменения данных — executeUpdate().

Разновидности Statement

Самый простой интерфейс Statement мы уже видели. И хотя он вполне пригоден для работы, для сложных запросов он подходит не так хорошо. В некоторых источниках высказывается мнение, что использовать Statement вообще не надо — вместо него подходят более сложные и более функционально насыщенные интерфейсы.

PreparedStatement

Источник

Java зависает при попытке выполнить запрос SQL во второй раз подряд

Вопрос:

У меня есть некоторый код, который подключается к базе данных, проверяет, найдена ли запись на основе критерия, если запись найдена, возвращает 3 и выдает пользователю сообщение о том, что запись с этими данными уже существует. Если запись еще не существует, выполните запрос insert для ее создания.

Тесты :

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

Б) выполнение кода и возврат никаких результатов не приводит к его зависанию. (см. код ниже)

C) просмотрел другие подобные вопросы на SE, но, похоже, не может найти никаких решений моей проблемы.

Используемые ресурсы: ucanaccess-2.0.9.3, Netbeans IDE 8.1, JDK 1.8 (компилируется до 1.6 из-за проблем совместимости со старыми версиями в компании), MS Access база данных по корпоративной сети

Пожалуйста, не обращайте внимания на неопрятность (если только это не является причиной), очистка ее в данный момент является WIP.

1 ответ

Недавно я начал разработку с android sdk. В настоящее время я пытаюсь добавить код для использования меню, однако при попытке добавить menu.xml под res/menu, Eclipse каждый раз зависает и зависает. Когда я снова открываю Eclipse, menu.xml находится там, но каждый раз, когда я пытаюсь открыть его.

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

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

Похожие вопросы:

Я создаю систему регистрации. Я использую node, express, и моя база данных-PostgreSQL. Я использую последний модуль pg для связи с Postgre. Моя функция получения пользователя универсальна, она.

Мы держим объект пользователя в сеансе. Когда мы обновляем его в первый раз (используя entityManager.merge( ent ))), он работает нормально; но во второй раз я получаю следующее исключение.

Я получаю ошибку при попытке выполнить динамический запрос вставки в volt db с помощью функции voltQueueSQLExperimental(). SQL в порядке, так как я запустил его отдельно в веб-студии volt. Ошибка.

Недавно я начал разработку с android sdk. В настоящее время я пытаюсь добавить код для использования меню, однако при попытке добавить menu.xml под res/menu, Eclipse каждый раз зависает и зависает.

Я использовал GetGeoPositionAsync() в своем приложении, и он отлично работает в коде другой функции, однако при использовании его в функции, которую я сейчас реализую, он зависает при первой попытке.

У меня есть фрагмент кода, отображаемый ниже. Моя проблема заключается в том, что код работает только во второй (третий и так далее) раз, когда он представлен. Я ничего не меняю между двумя.

Каждый раз, когда я получаю одно и то же время выполнения при выполнении запроса SQL. есть ли шанс получить одно и то же время выполнения за все время, если запрос SQL выполняется несколько раз?

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

Я успешно построил свой код angular и смог опубликовать его в репозитории Github. при публикации я также упомянул каталог dist/projectname Таким образом, я могу видеть в репозитории Github.

Источник

406 ошибка при попытке выполнить запрос sql с java

Привет всем, кто впервые публикует пост, так что я уверен, что пропущу некоторые стандарты стека.

В любом случае я получаю ошибку 406 при попытке запустить запрос sql с java, хотя я могу подключиться без запуска запроса sql (в моем коде, если я выну os.write(queryBody.getBytes()); я подключусь, но, очевидно, не смогу запустить свой запрос sql. Любой инсайт на этот счет был бы великолепен, я проверял, не сделал ли я что-то не так в своем запросе, но, похоже, это правильно. Я также пробовал клиент REST, но он не поддерживает никакого сертификационного соединения. Вот соответствующий код:

1 ответ

Я получаю ошибку 406 с Mechanize при попытке открыть URL: for url in urls: if http:// not in url: url = http:// + url print url try: page = mech.open(%s % url) except urllib2.HTTPError, e: print there was an error opening the URL, logging it print e.code logfile = open (log/urlopenlog.txt, a).

У меня есть веб-сайт, который использует XRM SDK для запроса CRM. По большей части я использую выражения запросов, но я делаю агрегированный подсчет на одной странице, и, следовательно, я использую запрос Fetch XML. Я отладил веб-сайт на своей локальной машине разработчика, указав на сервер Dev.

Чтобы исправить эту проблему, я изменил строку uc.setRequestProperty(«принятие», «application/json»); To uc.setRequestProperty(«Content-Type», «application/json»);

Похожие вопросы:

Я получаю ошибку при попытке выполнить динамический запрос вставки в volt db с помощью функции voltQueueSQLExperimental(). SQL в порядке, так как я запустил его отдельно в веб-студии volt. Ошибка.

Я пытаюсь выполнить запрос, используя PreparedStatement в Java. Я получаю номер ошибки 1064, когда пытаюсь выполнить свой запрос (синтаксическая ошибка). Я проверил это в браузере запросов MySQL с.

Я получаю ошибку 406 с Mechanize при попытке открыть URL: for url in urls: if http:// not in url: url = http:// + url print url try: page = mech.open(%s % url) except urllib2.HTTPError, e: print.

У меня есть веб-сайт, который использует XRM SDK для запроса CRM. По большей части я использую выражения запросов, но я делаю агрегированный подсчет на одной странице, и, следовательно, я использую.

Я установил bugzilla на свою cpanel. Но при создании новой ошибки он отображает ошибку 406 неприемлемый Соответствующее представление запрошенного ресурса /bugzilla/post_bug.cgi не удалось найти на.

DataReader выдает сообщение об ошибке при попытке выполнить команду на моей странице vb.net А ошибка выброса кода такова: Dim connectionString As String Dim connection As SqlConnection Dim sql As.

При попытке выполнить запрос bq я получаю следующую ошибку: FATAL Flags parsing error: Unknown command line flag ‘-v’ Запрос прост, и я думаю, что проблема находится внутри функции concat, потому.

При попытке выполнить приведенный ниже запрос я получаю синтаксическую ошибку Мне нужно обновить значение столбца из таблицы civicrm_address и переместить его из базы данных abc_abc_drupal_civi_4_17.

Источник

Инъекция SQL в Java и как ее легко предотвратить

Что такое SQL-инъекция? Инъекция SQL является одной из 10 основных уязвимостей веб-приложений. Проще говоря, SQL-инъекция означает введение/вставку SQL

Что такое SQL-инъекция?

Инъекция SQL является одной из 10 лучших уязвимостей веб-приложений. Проще говоря, SQL-инъекция означает ввод/вставку SQL-кода в запрос с помощью введенных пользователем данных. Это может произойти в любых приложениях, использующих реляционные базы данных, такие как Oracle, MySQL, PostgreSQL и SQL Server.

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

Влияние SQL-инъекции

Как Работает SQL-Инъекция?

Давайте взглянем на приведенный ниже пример кода.

1. Допустимый Пользовательский Ввод

Когда приведенный выше запрос выполняется с действительными данными, т. е. значением идентификатора пользователя 132, он будет выглядеть следующим образом.

Входные данные: 132

Выполненный запрос: выберите * из tbluser, где

Результат: Запрос вернет данные пользователя, имеющего идентификатор пользователя 132. В этом случае никакой SQL-инъекции не происходит.

2. Хакерский Ввод Данных Пользователем

Хакер может изменять запросы пользователей с помощью таких инструментов, как Postman, cURL и т. Д. для отправки SQL-кода в виде данных и таким образом в обход любых проверок на стороне пользовательского интерфейса.

Входные данные: 2 или

Выполненный запрос: выберите * из tbluser, где или

Результат: Теперь в приведенном выше запросе есть два условия с SQL ИЛИ выражением.

Типы SQL-инъекций

Давайте рассмотрим четыре типа SQL-инъекций.

1. SQL-инъекция на основе логических значений

Приведенный выше пример представляет собой случай SQL-инъекции на основе логических значений. Он использует логическое выражение, которое принимает значение true или false. Его можно использовать для получения дополнительной информации из базы данных. Например;

Входные данные: 2 или

SQL-запрос: выберите имя, фамилию из tbl_employee, где EmpID= 2 или

2. Инъекция SQL На основе Объединения

Оператор SQL union объединяет данные из двух разных запросов с одинаковым количеством столбцов. В этом случае оператор объединения используется для получения данных из других таблиц.

Входные данные: 2. выберите имя пользователя, пароль от tbluser

Запрос: Выберите имя, фамилию из tbl_employee, где EmpID= 2. Выберите имя пользователя, пароль из tbluser

Используя SQL-инъекцию на основе объединения, злоумышленник может получить учетные данные пользователя.

3. Инъекция SQL на основе времени

Входные данные: 2 + СПЯЩИЙ РЕЖИМ(5)

Запрос: выберите emp_id, имя, фамилию из tbl_employee, где + SLEEP(5)

В приведенном выше примере выполнение запроса будет приостановлено на 5 секунд.

4. Инъекция SQL на основе ошибок

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

Пример внедрения Java SQL

Сервер входа в систему получает имя пользователя и пароль из запроса и проверяет их на соответствие значениям базы данных. Если аутентификация прошла успешно, сервлет перенаправляет пользователя на домашнюю страницу, в противном случае он вернет ошибку.

Login.html Код :

LoginServlet.java Код :

Запросы к базе данных [MySQL]:

1. При вводе действительного имени пользователя и пароля со страницы входа в систему

Введите имя пользователя : джон

Введите имя пользователя : секретно

Запрос : выберите * из tbluser, где и

Результат : Имя пользователя и пароль существуют в базе данных, поэтому аутентификация прошла успешно. Пользователь будет перенаправлен на домашнюю страницу.

2. Получение несанкционированного доступа к системе с помощью SQL-инъекции

Введите имя пользователя : фиктивный

Введите пароль : ‘или ‘1’=’1

Запрос : выберите * из tbluser, где и пароль = ” или “1”=”1″

Результат : Введенные имя пользователя и пароль не существуют в базе данных, но аутентификация прошла успешно. Почему?

Это связано с внедрением SQL, так как мы ввели ‘ или ‘1’=’1 в качестве пароля. В запросе есть 3 условия.

В приведенном выше сценарии мы использовали логическое выражение для выполнения SQL-инъекции. Есть несколько других способов сделать SQL-инъекцию. В следующем разделе мы рассмотрим способы предотвращения внедрения SQL в наше Java-приложение.

Предотвращение внедрения SQL в Java-код

Самое простое решение-использовать PreparedStatement вместо Оператора для выполнения запроса.

Вместо объединения имени пользователя и пароля в запросе мы предоставляем их для запроса с помощью методов настройки Preparedstatement.

Теперь значение имени пользователя и пароля, полученные из запроса, обрабатываются только как данные, поэтому никаких SQL-инъекций не произойдет.

Давайте посмотрим на измененный код сервлета.

Давайте разберемся, что происходит в данном случае.

Рекомендации по предотвращению внедрения SQL

Это все для инъекции Java SQL, я надеюсь, что здесь не пропущено ничего важного.

Вы можете скачать пример проекта веб-приложения java по ссылке ниже.

Источник

[Перевод] Обработка ошибок и транзакций в SQL Server. Часть 1. Обработка ошибок – быстрый старт

Привет, Хабр! Представляю вашему вниманию перевод статьи «Error and Transaction Handling in SQL Server. Part One – Jumpstart Error Handling» автора Erland Sommarskog.

1. Введение

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

С другой стороны, если вы ставите под сомнение мои рекомендации, вам определенно необходимо прочитать две остальные части, где я погружаюсь в детали намного более глубоко, исследуя очень запутанный мир обработки ошибок и транзакций в SQL Server. Вторая и третья части, так же, как и три приложения, предназначены для читателей с более глубоким опытом. Первая статья — короткая, вторая и третья значительно длиннее.

Все статьи описывают обработку ошибок и транзакций в SQL Server для версии 2005 и более поздних версий.

1.1 Зачем нужна обработка ошибок?

Почему мы обрабатываем ошибки в нашем коде? На это есть много причин. Например, на формах в приложении мы проверяем введенные данные и информируем пользователей о допущенных при вводе ошибках. Ошибки пользователя – это предвиденные ошибки. Но нам также нужно обрабатывать непредвиденные ошибки. То есть, ошибки могут возникнуть из-за того, что мы что-то упустили при написании кода. Простой подход – это прервать выполнение или хотя бы вернуться на этап, в котором мы имеем полный контроль над происходящим. Недостаточно будет просто подчеркнуть, что совершенно непозволительно игнорировать непредвиденные ошибки. Это недостаток, который может вызвать губительные последствия: например, стать причиной того, что приложение будет предоставлять некорректную информацию пользователю или, что еще хуже, сохранять некорректные данные в базе. Также важно сообщать о возникновении ошибки с той целью, чтобы пользователь не думал о том, что операция прошла успешно, в то время как ваш код на самом деле ничего не выполнил.

Мы часто хотим, чтобы в базе данных изменения были атомарными. Например, задача по переводу денег с одного счета на другой. С этой целью мы должны изменить две записи в таблице CashHoldings и добавить две записи в таблицу Transactions. Абсолютно недопустимо, чтобы ошибки или сбой привели к тому, что деньги будут переведены на счет получателя, а со счета отправителя они не будут списаны. По этой причине обработка ошибок также касается и обработки транзакций. В приведенном примере нам нужно обернуть операцию в BEGIN TRANSACTION и COMMIT TRANSACTION, но не только это: в случае ошибки мы должны убедиться, что транзакция откачена.

2. Основные команды

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

2.1 TRY-CATCH

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

Как правило, в CATCH откатывают любую открытую транзакцию и повторно вызывают ошибку. Таким образом, вызывающая клиентская программа понимает, что что-то пошло не так. Повторный вызов ошибки мы обсудим позже в этой статье.

Вот очень быстрый пример:

Результат выполнения: This is the error: Divide by zero error encountered.

Мы вернемся к функции error_message() позднее. Стоит отметить, что использование PRINT в обработчике CATCH приводится только в рамках экспериментов и не следует делать так в коде реального приложения.

Если вызывает хранимую процедуру или запускает триггеры, то любая ошибка, которая в них возникнет, передаст выполнение в блок CATCH. Если более точно, то, когда возникает ошибка, SQL Server раскручивает стек до тех пор, пока не найдёт обработчик CATCH. И если такого обработчика нет, SQL Server отправляет сообщение об ошибке напрямую клиенту.

Есть одно очень важное ограничение у конструкции TRY-CATCH, которое нужно знать: она не ловит ошибки компиляции, которые возникают в той же области видимости. Рассмотрим пример:

Как можно видеть, блок TRY присутствует, но при возникновении ошибки выполнение не передается блоку CATCH, как это ожидалось. Это применимо ко всем ошибкам компиляции, таким как пропуск колонок, некорректные псевдонимы и тому подобное, которые возникают во время выполнения. (Ошибки компиляции могут возникнуть в SQL Server во время выполнения из-за отложенного разрешения имен – особенность, благодаря которой SQL Server позволяет создать процедуру, которая обращается к несуществующим таблицам.)

Эти ошибки не являются полностью неуловимыми; вы не можете поймать их в области, в которой они возникают, но вы можете поймать их во внешней области. Добавим такой код к предыдущему примеру:

Теперь мы получим на выходе это:

На этот раз ошибка была перехвачена, потому что сработал внешний обработчик CATCH.

2.2 SET XACT_ABORT ON

В начало ваших хранимых процедур следует всегда добавлять это выражение:

Оно активирует два параметра сессии, которые выключены по умолчанию в целях совместимости с предыдущими версиями, но опыт доказывает, что лучший подход – это иметь эти параметры всегда включенными. Поведение SQL Server по умолчанию в той ситуации, когда не используется TRY-CATCH, заключается в том, что некоторые ошибки прерывают выполнение и откатывают любые открытые транзакции, в то время как с другими ошибками выполнение последующих инструкций продолжается. Когда вы включаете XACT_ABORT ON, почти все ошибки начинают вызывать одинаковый эффект: любая открытая транзакция откатывается, и выполнение кода прерывается. Есть несколько исключений, среди которых наиболее заметным является выражение RAISERROR.

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

Для надежной обработки ошибок в SQL Server вам необходимы как TRY-CATCH, так и SET XACT_ABORT ON. Среди них инструкция SET XACT_ABORT ON наиболее важна. Если для кода на промышленной среде только на нее полагаться не стоит, то для быстрых и простых решений она вполне подходит.

Параметр NOCOUNT не имеет к обработке ошибок никакого отношения, но включение его в код является хорошей практикой. NOCOUNT подавляет сообщения вида (1 row(s) affected), которые вы можете видеть в панели Message в SQL Server Management Studio. В то время как эти сообщения могут быть полезны при работе c SSMS, они могут негативно повлиять на производительность в приложении, так как увеличивают сетевой трафик. Сообщение о количестве строк также может привести к ошибке в плохо написанных клиентских приложениях, которые могут подумать, что это данные, которые вернул запрос.

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

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

3. Основной пример обработки ошибок

После того, как мы посмотрели на TRY-CATCH и SET XACT_ABORT ON, давайте соединим их вместе в примере, который мы можем использовать во всех наших хранимых процедурах. Для начала я покажу пример, в котором ошибка генерируется в простой форме, а в следующем разделе я рассмотрю решения получше.

Для примера я буду использовать эту простую таблицу.

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

Первая строка в процедуре включает XACT_ABORT и NOCOUNT в одном выражении, как я показывал выше. Эта строка – единственная перед BEGIN TRY. Все остальное в процедуре должно располагаться после BEGIN TRY: объявление переменных, создание временных таблиц, табличных переменных, всё. Даже если у вас есть другие SET-команды в процедуре (хотя причины для этого встречаются редко), они должны идти после BEGIN TRY.

Причина, по которой я предпочитаю указывать SET XACT_ABORT и NOCOUNT перед BEGIN TRY, заключается в том, что я рассматриваю это как одну строку шума: она всегда должна быть там, но я не хочу, чтобы это мешало взгляду. Конечно же, это дело вкуса, и если вы предпочитаете ставить SET-команды после BEGIN TRY, ничего страшного. Важно то, что вам не следует ставить что-либо другое перед BEGIN TRY.

Часть между BEGIN TRY и END TRY является основной составляющей процедуры. Поскольку я хотел использовать транзакцию, определенную пользователем, я ввел довольно надуманное бизнес-правило, в котором говорится, что если вы вставляете пару, то обратная пара также должна быть вставлена. Два выражения INSERT находятся внутри BEGIN и COMMIT TRANSACTION. Во многих случаях у вас будет много строк кода между BEGIN TRY и BEGIN TRANSACTION. Иногда у вас также будет код между COMMIT TRANSACTION и END TRY, хотя обычно это только финальный SELECT, возвращающий данные или присваивающий значения выходным параметрам. Если ваша процедура не выполняет каких-либо изменений или имеет только одно выражение INSERT/UPDATE/DELETE/MERGE, то обычно вам вообще не нужно явно указывать транзакцию.

В то время как блок TRY будет выглядеть по-разному от процедуры к процедуре, блок CATCH должен быть более или менее результатом копирования и вставки. То есть вы делаете что-то короткое и простое и затем используете повсюду, не особо задумываясь. Обработчик CATCH, приведенный выше, выполняет три действия:

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

Код повторной генерации ошибки включает такую строку:

Встроенная функция error_message() возвращает текст возникшей ошибки. В следующей строке ошибка повторно вызывается с помощью выражения RAISERROR. Это не самый простой способ вызова ошибки, но он работает. Другие способы мы рассмотрим в следующей главе.

Замечание: синтаксис для присвоения начального значения переменной в DECLARE был внедрен в SQL Server 2008. Если у вас SQL Server 2005, вам нужно разбить строку на DECLARE и выражение SELECT.

Финальное выражение RETURN – это страховка. RAISERROR никогда не прерывает выполнение, поэтому выполнение следующего выражения будет продолжено. Пока все процедуры используют TRY-CATCH, а также весь клиентский код обрабатывает исключения, нет повода для беспокойства. Но ваша процедура может быть вызвана из старого кода, написанного до SQL Server 2005 и до внедрения TRY-CATCH. В те времена лучшее, что мы могли делать, это смотреть на возвращаемые значения. То, что вы возвращаете с помощью RETURN, не имеет особого значения, если это не нулевое значение (ноль обычно обозначает успешное завершение работы).

Последнее выражение в процедуре – это END CATCH. Никогда не следует помещать какой-либо код после END CATCH. Кто-нибудь, читающий процедуру, может не увидеть этот кусок кода.

После прочтения теории давайте попробуем тестовый пример:

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

Мы получили корректное сообщение об ошибке, но если вы посмотрите на заголовки этого сообщения и на предыдущее поближе, то можете заметить проблему:

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

4. Три способа генерации ошибки

4.1 Использование error_handler_sp

Мы рассмотрели функцию error_message(), которая возвращает текст сообщения об ошибке. Сообщение об ошибке состоит из нескольких компонентов, и существует своя функция error_xxx() для каждого из них. Мы можем использовать их для повторной генерации полного сообщения, которое содержит оригинальную информацию, хотя и в другом формате. Если делать это в каждом обработчике CATCH, это будет большой недостаток — дублирование кода. Вам не обязательно находиться в блоке CATCH для вызова error_message() и других подобных функций, и они вернут ту же самую информацию, если будут вызваны из хранимой процедуры, которую выполнит блок CATCH.

Позвольте представить вам error_handler_sp:

Первое из того, что делает error_handler_sp – это сохраняет значение всех error_xxx() функций в локальные переменные. Я вернусь к выражению IF через секунду. Вместо него давайте посмотрим на выражение SELECT внутри IF:

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

Отформатированное сообщение об ошибке начинается с трех звездочек. Этим достигаются две цели: 1) Мы можем сразу видеть, что это сообщение вызвано из обработчика CATCH. 2) Это дает возможность для error_handler_sp отфильтровать ошибки, которые уже были сгенерированы один или более раз, с помощью условия NOT LIKE ‘***%’ для того, чтобы избежать изменения сообщения во второй раз.

Вот как обработчик CATCH должен выглядеть, когда вы используете error_handler_sp:

Давайте попробуем несколько тестовых сценариев.

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

Я покажу еще два метода вызова ошибок. Однако error_handler_sp является моей главной рекомендацией для читателей, которые читают эту часть. Это — простой вариант, который работает на всех версиях SQL Server начиная с 2005. Существует только один недостаток: в некоторых случаях SQL Server генерирует два сообщения об ошибках, но функции error_xxx() возвращают только одну из них, и поэтому одно из сообщений теряется. Это может быть неудобно при работе с административными командами наподобие BACKUP\RESTORE, но проблема редко возникает в коде, предназначенном чисто для приложений.

4.2. Использование ;THROW

В SQL Server 2012 Microsoft представил выражение ;THROW для более легкой обработки ошибок. К сожалению, Microsoft сделал серьезную ошибку при проектировании этой команды и создал опасную ловушку.

С выражением ;THROW вам не нужно никаких хранимых процедур. Ваш обработчик CATCH становится таким же простым, как этот:

Достоинство ;THROW в том, что сообщение об ошибке генерируется точно таким же, как и оригинальное сообщение. Если изначально было два сообщения об ошибках, оба сообщения воспроизводятся, что делает это выражение еще привлекательнее. Как и со всеми другими сообщениями об ошибках, ошибки, сгенерированные ;THROW, могут быть перехвачены внешним обработчиком CATCH и воспроизведены. Если обработчика CATCH нет, выполнение прерывается, поэтому оператор RETURN в данном случае оказывается не нужным. (Я все еще рекомендую оставлять его, на случай, если вы измените свое отношение к ;THROW позже).

Если у вас SQL Server 2012 или более поздняя версия, измените определение insert_data и outer_sp и попробуйте выполнить тесты еще раз. Результат в этот раз будет такой:

Имя процедуры и номер строки верны и нет никакого другого имени процедуры, которое может нас запутать. Также сохранены оригинальные номера ошибок.

В этом месте вы можете сказать себе: действительно ли Microsoft назвал команду ;THROW? Разве это не просто THROW? На самом деле, если вы посмотрите в Books Online, там не будет точки с запятой. Но точка с запятой должны быть. Официально они отделяют предыдущее выражение, но это опционально, и далеко не все используют точку с запятой в выражениях T-SQL. Более важно, что если вы пропустите точку с запятой перед THROW, то не будет никакой синтаксической ошибки. Но это повлияет на поведение при выполнении выражения, и это поведение будет непостижимым для непосвященных. При наличии активной транзакции вы получите сообщение об ошибке, которое будет полностью отличаться от оригинального. И еще хуже, что при отсутствии активной транзакции ошибка будет тихо выведена без обработки. Такая вещь, как пропуск точки с запятой, не должно иметь таких абсурдных последствий. Для уменьшения риска такого поведения, всегда думайте о команде как о ;THROW (с точкой с запятой).

Нельзя отрицать того, что ;THROW имеет свои преимущества, но точка с запятой не единственная ловушка этой команды. Если вы хотите использовать ее, я призываю вас прочитать по крайней мере вторую часть этой серии, где я раскрываю больше деталей о команде ;THROW. До этого момента, используйте error_handler_sp.

4.3. Использование SqlEventLog

Третий способ обработки ошибок – это использование SqlEventLog, который я описываю очень детально в третьей части. Здесь я лишь сделаю короткий обзор.

SqlEventLog предоставляет хранимую процедуру slog.catchhandler_sp, которая работает так же, как и error_handler_sp: она использует функции error_xxx() для сбора информации и выводит сообщение об ошибке, сохраняя всю информацию о ней. Вдобавок к этому, она логирует ошибку в таблицу splog.sqleventlog. В зависимости от типа приложения, которое у вас есть, эта таблица может быть очень ценным объектом.

Для использования SqlEventLog, ваш обработчик CATCH должен быть таким:

@@procid возвращает идентификатор объекта текущей хранимой процедуры. Это то, что SqlEventLog использует для логирования информации в таблицу. Используя те же тестовые сценарии, получим результат их работы с использованием catchhandler_sp:

5. Финальные замечания

Вы изучили основной образец для обработки ошибок и транзакций в хранимых процедурах. Он не идеален, но он должен работать в 90-95% вашего кода. Есть несколько ограничений, на которые стоит обратить внимание:

Перед тем как закончить, я хочу кратко коснуться триггеров и клиентского кода.

Триггеры

Пример для обработки ошибок в триггерах не сильно отличается от того, что используется в хранимых процедурах, за исключением одной маленькой детали: вы не должны использовать выражение RETURN (потому что RETURN не допускается использовать в триггерах).

С триггерами важно понимать, что они являются частью команды, которая запустила триггер, и в триггере вы находитесь внутри транзакции, даже если не используете BEGIN TRANSACTION.
Иногда я вижу на форумах людей, которые спрашивают, могут ли они написать триггер, который не откатывает в случае падения запустившую его команду. Ответ таков: нет способа сделать это надежно, поэтому не стоит даже пытаться. Если в этом есть необходимость, по возможности не следует использовать триггер вообще, а найти другое решение. Во второй и третьей частях я рассматриваю обработку ошибок в триггерах более подробно.

Клиентский код

У вас должна быть обработка ошибок в коде клиента, если он имеет доступ к базе. То есть вы должны всегда предполагать, что при любом вызове что-то может пойти не так. Как именно внедрить обработку ошибок, зависит от конкретной среды.

Здесь я только обращу внимание на важную вещь: реакцией на ошибку, возвращенную SQL Server, должно быть завершение запроса во избежание открытых бесхозных транзакций:

Это также применимо к знаменитому сообщению Timeout expired (которое является не сообщением от SQL Server, а от API).

6. Конец первой части

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

… и не забывайте добавлять эту строку в начало ваших хранимых процедур:

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *