Управление транзакциями кэширование памяти перехват исключительных ситуаций и обработка ошибок
Привет, Хабр! Представляю вашему вниманию перевод статьи «Error and Transaction Handling in SQL Server. Part One – Jumpstart Error Handling» автора Erland Sommarskog.
Физическая организация базы данных формата InterBase
БД состоит из последовательности страниц, нумеруемых с нуля.
Нулевая страница является служебной и содержит информацию, необходимую для соединения с БД.
Размер страницы – 1 Кб (по умолчанию), а также 2, 4, 8 Кб – установлен при создании и может быть изменен при сохранении и восстановлении БД, поэтому размер страницы рекомендуется задавать равным размеру кластера диска. При этом учитывается длина записи и наличие BLOB-полей. Объем буфера ввода-вывода для операции чтения и записи определяется количеством страниц (75 по умолчанию). Если БД будет читаться чаще, объем следует увеличить.
В InterBase поддерживается многоверсионная структура записей. При изменении записи создается новая версия, куда записывается, помимо данных, номер транзакции и указатель на предыдущую версию. Старая версия помечена как измененная, а её указатель на следующую версию содержит адрес вновь созданной версии. Каждая стартующая транзакция работает с измененной версией записи. При удалении записи она помечается как удаленная, а удаляется, когда с ней завершены все активные транзакции.
При добавлении новой записи происходит компресация страницы за счет использования "дырок". Если суммарной "дырки" не хватает, то запись помещается на новую страницу. Выделение страниц не оптимизировано. На служебной странице хранятся номера всех свободных страниц.
Многоверсионная структура записи и неоптимальное выделение страниц ведут к высокой фрагментации и замедлению работы с БД, поэтому периодически приходится выполнять дефрагментацию либо путем сохранения БД на диске с последующим восстановлением из резерва копий, либо путем автосборки "мусора".
4.2. Использование ;THROW
В SQL Server 2012 Microsoft представил выражение ;THROW для более легкой обработки ошибок. К сожалению, Microsoft сделал серьезную ошибку при проектировании этой команды и создал опасную ловушку.
С выражением ;THROW вам не нужно никаких хранимых процедур. Ваш обработчик CATCH становится таким же простым, как этот:
Если у вас SQL Server 2012 или более поздняя версия, измените определение insert_data и outer_sp и попробуйте выполнить тесты еще раз. Результат в этот раз будет такой:
Имя процедуры и номер строки верны и нет никакого другого имени процедуры, которое может нас запутать. Также сохранены оригинальные номера ошибок.
Нельзя отрицать того, что ;THROW имеет свои преимущества, но точка с запятой не единственная ловушка этой команды. Если вы хотите использовать ее, я призываю вас прочитать по крайней мере вторую часть этой серии, где я раскрываю больше деталей о команде ;THROW. До этого момента, используйте error_handler_sp.
Перехват исключительных ситуаций и обработка ошибок
Структурная схема терминов
Клиентский код
У вас должна быть обработка ошибок в коде клиента, если он имеет доступ к базе. То есть вы должны всегда предполагать, что при любом вызове что-то может пойти не так. Как именно внедрить обработку ошибок, зависит от конкретной среды.
Здесь я только обращу внимание на важную вещь: реакцией на ошибку, возвращенную SQL Server, должно быть завершение запроса во избежание открытых бесхозных транзакций:
4.3. Использование SqlEventLog
Третий способ обработки ошибок – это использование SqlEventLog, который я описываю очень детально в третьей части. Здесь я лишь сделаю короткий обзор.
Для использования SqlEventLog, ваш обработчик CATCH должен быть таким:
@@procid возвращает идентификатор объекта текущей хранимой процедуры. Это то, что SqlEventLog использует для логирования информации в таблицу. Используя те же тестовые сценарии, получим результат их работы с использованием catchhandler_sp:
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 наиболее важна. Если для кода на промышленной среде только на нее полагаться не стоит, то для быстрых и простых решений она вполне подходит.
Выше я использовал синтаксис, который немного необычен. Большинство людей написали бы два отдельных выражения:
Между ними нет никакого отличия. Я предпочитаю версию с SET и запятой, т.к. это снижает уровень шума в коде. Поскольку эти выражения должны появляться во всех ваших хранимых процедурах, они должны занимать как можно меньше места.
Понятие исключительных ситуаций
Исключительная ситуация – это динамическая ошибка, представляющая собой нарушение условий выполнения программы, вызывающее прерывание или полное прекращение ее работы.
Исключением называют обработчик исключительной ситуации, который производит нейтрализацию вызвавшей его динамической ошибки.
В Delphi механизмы обработки ошибок инкапсулированы в класс Exception, описываемый в модуле SysUtils. Все классы исключений являются его потомками.
Возникающие при выполнении программы динамические ошибки автоматически преобразовываются средствами Delphi в соответствующие объекты-исключения. Объект-исключение содержит информацию о типе ошибки и при возникновении исключения заставляет программу или ее поток (составляющую процесса) временно приостановиться. После обработки исключения объекты-исключения автоматически удаляются.
Исключительные ситуации могут возникнуть по причине ошибок в среде Windows, а также нехватки памяти, ошибок преобразования, в результате вычислений и т.п.
Для операций, связанных с БД, существуют специальные дополнительные классы исключений:
EDatabaseError – предназначен для обработки ошибок при работе с набором данных (БД – TTable, TQuery), имеет двух потомков:
EDBEngineError – ошибка BDE (для локальных и файл-серверных БД);
EDBClient – ошибка в приложении клиента (для клиент-серверных БД).
EDBEditError – значение, введенное в поле, не соответствует типу данных поля или вводимые в поле данные несовместимы с маской ввода, заданной в свойстве EditMask.
Исключительная ситуация класса EDatabaseError генерируется, например, при попытке открытия набора данных, связанного с отсутствующей таблицей, при изменении записи набора данных, находящегося в режиме просмотра.
Класс TDBError содержит информацию об исключительной ситуации в свойствах:
ErrorCode типа DBIResult – код ошибки;
Category типа Byte – категория исключительной ситуации;
SubCode типа Byte – группа (подкод) исключительной ситуации;
NativeError типа Longint – код ошибки, возвращаемой сервером. Если код равен 0, то ошибка произошла не на сервере.
Тут вы можете оставить комментарий к выбранному абзацу или сообщить об ошибке.
Все серверы БД имеют похожие принципы организации данных и управления ими. Локальный сервер InterBase представляет собой локальную копию (версию) настоящего сервера Interbase, он устанавливается на компьютер локально, и позволяет локально разрабатывать и отлаживать приложения для работы с удаленными БД. В результате становится возможным так называемое масштабирование БД. Приложение, отлаженное локально для Local InterBase, может быть без всяких изменений перенесено на удаленный компьютер и будет работать с удаленной базой данных. SQL-сервер InterBase является "промышленной" СУБД, предназначается для хранения и выдачи больших объемов информации, в условиях одновременной работы множества клиентских приложений.
Целостность БД задается путем использования:
Отношения подчиненности между таблицами путем задания первичных ключей у родительской таблицы (primary) и внешних – у дочерних (foreign).
Ограничения на значения отдельных столбцов путем определения ограничений (constraint) на значение домена или столбца.
Бизнес-правил, задаваемых при помощи триггеров, т.е. подпрограмм, автоматически выполняемых сервером до или (и) после события изменения записи в БД.
Уникальных значений нужных полей путем создания и использования генераторов (generator).
Бизнес-правила – это правила работы с БД, позволяющие быстро модифицировать ее структуру, изменять значения ключевых полей, перестраивать индексы.
Для ускорения работы клиентских приложений с удаленной БД могут быть использованы хранимые процедуры(stored procedure), т.е. процедуры, которые хранятся и выполняются на сервере.
В составе записей БД могут входить BLOB-поля, предназначенные для хранения больших объемов информации в двоичном виде: текст, графические документы, файлы, мультимедиа и т.д. Интерпретация BLOB-полей выполняется в клиентском приложении, однако разработчик может определить BLOB-фильтры для автоматического преобразования BLOB-поля в другой вид.
InterBase позволяет определить UDF (user defined function) – пользовательские функции, в которых могут реализовываться подпрограммы, отсутствующие в стандартных функциях InterBase, например, вычисление максимума и минимума, преобразование типов, и т.п. Функция пишется на любом языке программирования в виде DLL.
InterBase может посылать уведомления клиентским приложениям о наступлении события. Для обеспечения быстрого выполнения запросов можно создавать виртуальные таблицы, в которых объединены записи в соответствии с некоторым условием.
5. Финальные замечания
Вы изучили основной образец для обработки ошибок и транзакций в хранимых процедурах. Он не идеален, но он должен работать в 90-95% вашего кода. Есть несколько ограничений, на которые стоит обратить внимание:
Перед тем как закончить, я хочу кратко коснуться триггеров и клиентского кода.
1. Введение
Эта статья – первая в серии из трёх статей, посвященных обработке ошибок и транзакций в SQL Server. Её цель – дать вам быстрый старт в теме обработки ошибок, показав базовый пример, который подходит для большей части вашего кода. Эта часть написана в расчете на неопытного читателя, и по этой причине я намеренно умалчиваю о многих деталях. В данный момент задача состоит в том, чтобы рассказать как без упора на почему. Если вы принимаете мои слова на веру, вы можете прочесть только эту часть и отложить остальные две для дальнейших этапов в вашей карьере.
С другой стороны, если вы ставите под сомнение мои рекомендации, вам определенно необходимо прочитать две остальные части, где я погружаюсь в детали намного более глубоко, исследуя очень запутанный мир обработки ошибок и транзакций в SQL Server. Вторая и третья части, так же, как и три приложения, предназначены для читателей с более глубоким опытом. Первая статья — короткая, вторая и третья значительно длиннее.
Все статьи описывают обработку ошибок и транзакций в SQL Server для версии 2005 и более поздних версий.
4.1 Использование error_handler_sp
Позвольте представить вам error_handler_sp:
Первое из того, что делает error_handler_sp – это сохраняет значение всех error_xxx() функций в локальные переменные. Я вернусь к выражению IF через секунду. Вместо него давайте посмотрим на выражение SELECT внутри IF:
Вот как обработчик CATCH должен выглядеть, когда вы используете error_handler_sp:
Давайте попробуем несколько тестовых сценариев.
2. Основные команды
Мы начнем с обзора наиболее важных команд, которые необходимы для обработки ошибок. Во второй части я опишу все команды, относящиеся к обработке ошибок и транзакций.
2.1 TRY-CATCH
Основным механизмом обработки ошибок является конструкция TRY-CATCH, очень напоминающая подобные конструкции в других языках. Структура такова:
Если какая-либо ошибка появится в , выполнение будет переведено в блок CATCH, и будет выполнен код обработки ошибок.
Как правило, в CATCH откатывают любую открытую транзакцию и повторно вызывают ошибку. Таким образом, вызывающая клиентская программа понимает, что что-то пошло не так. Повторный вызов ошибки мы обсудим позже в этой статье.
Вот очень быстрый пример:
Результат выполнения: This is the error: Divide by zero error encountered.
Мы вернемся к функции error_message() позднее. Стоит отметить, что использование PRINT в обработчике CATCH приводится только в рамках экспериментов и не следует делать так в коде реального приложения.
Есть одно очень важное ограничение у конструкции TRY-CATCH, которое нужно знать: она не ловит ошибки компиляции, которые возникают в той же области видимости. Рассмотрим пример:
Как можно видеть, блок TRY присутствует, но при возникновении ошибки выполнение не передается блоку CATCH, как это ожидалось. Это применимо ко всем ошибкам компиляции, таким как пропуск колонок, некорректные псевдонимы и тому подобное, которые возникают во время выполнения. (Ошибки компиляции могут возникнуть в SQL Server во время выполнения из-за отложенного разрешения имен – особенность, благодаря которой SQL Server позволяет создать процедуру, которая обращается к несуществующим таблицам.)
Эти ошибки не являются полностью неуловимыми; вы не можете поймать их в области, в которой они возникают, но вы можете поймать их во внешней области. Добавим такой код к предыдущему примеру:
Теперь мы получим на выходе это:
На этот раз ошибка была перехвачена, потому что сработал внешний обработчик CATCH.
4. Три способа генерации ошибки
1.1 Зачем нужна обработка ошибок?
Почему мы обрабатываем ошибки в нашем коде? На это есть много причин. Например, на формах в приложении мы проверяем введенные данные и информируем пользователей о допущенных при вводе ошибках. Ошибки пользователя – это предвиденные ошибки. Но нам также нужно обрабатывать непредвиденные ошибки. То есть, ошибки могут возникнуть из-за того, что мы что-то упустили при написании кода. Простой подход – это прервать выполнение или хотя бы вернуться на этап, в котором мы имеем полный контроль над происходящим. Недостаточно будет просто подчеркнуть, что совершенно непозволительно игнорировать непредвиденные ошибки. Это недостаток, который может вызвать губительные последствия: например, стать причиной того, что приложение будет предоставлять некорректную информацию пользователю или, что еще хуже, сохранять некорректные данные в базе. Также важно сообщать о возникновении ошибки с той целью, чтобы пользователь не думал о том, что операция прошла успешно, в то время как ваш код на самом деле ничего не выполнил.
Мы часто хотим, чтобы в базе данных изменения были атомарными. Например, задача по переводу денег с одного счета на другой. С этой целью мы должны изменить две записи в таблице CashHoldings и добавить две записи в таблицу Transactions. Абсолютно недопустимо, чтобы ошибки или сбой привели к тому, что деньги будут переведены на счет получателя, а со счета отправителя они не будут списаны. По этой причине обработка ошибок также касается и обработки транзакций. В приведенном примере нам нужно обернуть операцию в BEGIN TRANSACTION и COMMIT TRANSACTION, но не только это: в случае ошибки мы должны убедиться, что транзакция откачена.
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. Кто-нибудь, читающий процедуру, может не увидеть этот кусок кода.
После прочтения теории давайте попробуем тестовый пример:
Давайте добавим внешнюю процедуру для того, чтобы увидеть, что происходит при повторном вызове ошибки:
6. Конец первой части
Это конец первой из трех частей серии. Если вы хотели изучить вопрос обработки ошибок быстро, вы можете закончить чтение здесь. Если вы настроены идти дальше, вам следует прочитать вторую часть, где наше путешествие по запутанным джунглям обработки ошибок и транзакций в SQL Server начинается по-настоящему.
Механизм транзакций используется для поддержания целостности БД. Транзакция – это набор операций с БД, который переводит БД из одного целостного состояния в другое. Чтобы транзакция была успешной, должны выполниться все операции, входящие в ее состав. В случае возникновения ошибки хотя бы одной из операций вся транзакция считается неуспешной, и результаты всех операций отменяются. Транзакция может быть явной и неявной как при работе с локальными, так и при работе с удаленными БД.
Для удаленной БД, кроме поддержания целостности данных, механизм транзакций позволяет определить порядок взаимодействия запросов. Для соперничающих запросов устанавливается режим одновременного доступа к одним и тем же данным.
Неявная транзакция запускается и завершается автоматически при модификации набора данных. Утверждается методом Post, а отменяется Cancel для локальных БД. Для удаленных БД используется PassThrough SQL-запрос, его выполнение приводит к запуску неявной транзакции. Такой SQL-запрос по модификации данных выполняется с помощью метода ExecSQL компонента Query. Способ взаимодействия с сервером на уровне такой транзакции определяет параметр SQLPASSTHRU MODE псевдонима БД или драйвера (в нашем случае InterBase).
Явной транзакцией управляет программист. Для реализации механизма явных транзакций Delphi предоставляет специальные методы компонента DataBase:
StartTransaction – метод начинает транзакцию. После него располагаются инструкции, составляющие транзакцию. При выполнении операций производится обработка возникающих исключений.
Commit – подтверждает успешную транзакцию. После него все изменения вступают в силу.
Rollback – отменяет транзакцию и действия всех операций в ее рамках. Вызывается при возникновении исключения.
Можно использовать аналогичные инструкции языка SQL-сервера:
Инструкция запуска явной транзакции имеет формат:
[READ WRITE | READ ONLY]
Все операнды этой инструкции являются необязательными и позволяют управлять режимами транзакции:
Режимы доступа к данным:
READ WRITE – разрешены чтение и модификация записей (по умолчанию);
READ ONLY – разрешено только чтение записей.
Поведение в случае конфликта транзакций при обновлении записей:
WAIT – ожидание завершения другой транзакции (по умолчанию);
NO WAIT – прекращение данной транзакции.
Уровень изоляции от других транзакций (операнд ISOLATION LEVEL):
SNAPSHOT – чтение данных в состоянии на момент начала транзакции (по умолчанию); изменения, сделанные другими транзакциями, в данной транзакции не видны;
SNAPSHOT TABLE STABILITY – предоставление транзакции исключительного доступа к таблицам; другие транзакции могут читать записи из таблиц;
READ COMMITED – чтение только подтвержденных изменений в записях; если изменения еще не подтверждены, то читается предыдущая версия записи.
Блокирование таблиц, указанных в списке RESERVING, для других транзакций:
PROTECTED READ – разрешено только чтение записей;
PROTECTED WRITE – для транзакций с уровнем изоляции SNAPSHOT или READ COMMITED разрешена модификация записей;
SHARED READ – разрешены чтение и модификация записей;
SHARED WRITE – разрешено чтение записей, а для транзакций с уровнем изоляции SNAPSHOT или READ COMMITED разрешена модификация записей.
Тут вы можете оставить комментарий к выбранному абзацу или сообщить об ошибке.
Механизм транзакций используется для поддержания целостности БД. Транзакция – это набор операций с БД, который переводит БД из одного целостного состояния в другое. Чтобы транзакция была успешной, должны выполниться все операции, входящие в ее состав. В случае возникновения ошибки хотя бы одной из операций вся транзакция считается неуспешной, и результаты всех операций отменяются. Транзакция может быть явной и неявной как при работе с локальными, так и при работе с удаленными БД.
Для удаленной БД, кроме поддержания целостности данных, механизм транзакций позволяет определить порядок взаимодействия запросов. Для соперничающих запросов устанавливается режим одновременного доступа к одним и тем же данным.
Неявная транзакция запускается и завершается автоматически при модификации набора данных. Утверждается методом Post, а отменяется Cancel для локальных БД. Для удаленных БД используется PassThrough SQL-запрос, его выполнение приводит к запуску неявной транзакции. Такой SQL-запрос по модификации данных выполняется с помощью метода ExecSQL компонента Query. Способ взаимодействия с сервером на уровне такой транзакции определяет параметр SQLPASSTHRU MODE псевдонима БД или драйвера (в нашем случае InterBase).
Явной транзакцией управляет программист. Для реализации механизма явных транзакций Delphi предоставляет специальные методы компонента DataBase:
StartTransaction – метод начинает транзакцию. После него располагаются инструкции, составляющие транзакцию. При выполнении операций производится обработка возникающих исключений.
Commit – подтверждает успешную транзакцию. После него все изменения вступают в силу.
Rollback – отменяет транзакцию и действия всех операций в ее рамках. Вызывается при возникновении исключения.
^ SET TRANSACTION;
COMMIT;
ROLLBACK.
[READ WRITE | READ ONLY]
Все операнды этой инструкции являются необязательными и позволяют управлять режимами транзакции:
READ WRITE – разрешены чтение и модификация записей (по умолчанию);
READ ONLY – разрешено только чтение записей.
WAIT – ожидание завершения другой транзакции (по умолчанию);
NO WAIT – прекращение данной транзакции.
SNAPSHOT – чтение данных в состоянии на момент начала транзакции (по умолчанию); изменения, сделанные другими транзакциями, в данной транзакции не видны;
SNAPSHOT TABLE STABILITY – предоставление транзакции исключительного доступа к таблицам; другие транзакции могут читать записи из таблиц;
READ COMMITED – чтение только подтвержденных изменений в записях; если изменения еще не подтверждены, то читается предыдущая версия записи.
PROTECTED READ – разрешено только чтение записей;
PROTECTED WRITE – для транзакций с уровнем изоляции SNAPSHOT или READ COMMITED разрешена модификация записей;
SHARED READ – разрешены чтение и модификация записей;
SHARED WRITE – разрешено чтение записей, а для транзакций с уровнем изоляции SNAPSHOT или READ COMMITED разрешена модификация записей.
КЭШем называется специально выделенная область оперативной памяти (буфер).
Кэшированные изменения БД заключаются в том, что на компьютере клиента в КЭШе создается локальная копия данных, и все изменения в данных выполняются в этой копии. Сделанные изменения можно подтвердить, перенеся их в основную БД, или отказаться от них. Этот механизм напоминает транзакции, но, в отличие от них, существенно снижает нагрузку на сеть, т.к. все изменения в основную БД передаются одним пакетом. Однако для всех записей локальной копии отсутствуют блокировки на изменение их значений, в то время как для основной БД, находящейся на сервере, они могут быть в этот момент установлены другим приложением.
Механизм кэшированных изменений реализуется в приложении через компоненты DataBase, Table, Query (при доступе с помощью BDE), а также через специально предназначенный для этого компонент UpdateSQL. Компонент UpdateSQL используется для подтверждения кэшированных изменений и обеспечивает модификацию наборов данных, доступных только для чтения.
Для наборов данных DataBase, Table, Query включением режима кэшированных изменений управляет свойство CachedUpdates, принимающее два значения:
True – активизирует режим;
False – выключает его (по умолчанию).
запись кэшированных изменений в основную БД;
подтверждение или отмена сделанных изменений.
Метод CommitUpdates набора данных подтверждает изменение.
Метод CancelUpdates отменяет их, возвращая БД в исходное состояние.
^ ПЕРЕХВАТ ИСКЛЮЧИТЕЛЬНЫХ СИТУАЦИЙ И ОБРАБОТКА ОШИБОК
Структурная схема терминов
Понятие исключительных ситуаций
Исключительная ситуация – это динамическая ошибка, представляющая собой нарушение условий выполнения программы, вызывающее прерывание или полное прекращение ее работы.
Исключением называют обработчик исключительной ситуации, который производит нейтрализацию вызвавшей его динамической ошибки.
В Delphi механизмы обработки ошибок инкапсулированы в класс Exception, описываемый в модуле SysUtils. Все классы исключений являются его потомками.
Возникающие при выполнении программы динамические ошибки автоматически преобразовываются средствами Delphi в соответствующие объекты-исключения. Объект-исключение содержит информацию о типе ошибки и при возникновении исключения заставляет программу или ее поток (составляющую процесса) временно приостановиться. После обработки исключения объекты-исключения автоматически удаляются.
Исключительные ситуации могут возникнуть по причине ошибок в среде Windows, а также нехватки памяти, ошибок преобразования, в результате вычислений и т.п.
Для операций, связанных с БД, существуют специальные дополнительные классы исключений:
EDBEngineError – ошибка BDE (для локальных и файл-серверных БД);
EDBClient – ошибка в приложении клиента (для клиент-серверных БД).
Исключительная ситуация класса EDatabaseError генерируется, например, при попытке открытия набора данных, связанного с отсутствующей таблицей, при изменении записи набора данных, находящегося в режиме просмотра.
Для обработки исключений в приложении есть один глобальный обработчик и несколько специализированных обработчиков, реагирующих на определенные исключения. Каждое исключение обрабатывает свой специализированный локальный обработчик. Исключение, не имеющее своего локального обработчика, обрабатывается глобальным обработчиком приложения.
^ Глобальная обработка
Программист может выполнить более полную обработку, создав свой глобальный обработчик события OnException. Для этого удобно использовать компонент ApplicationEvents.
Procedure TForm1.ApplicationEvents1Exception (Sender: TObject; E: Exception);
MessageDlg (E. Message, mtError, [mbOK], 0);
// Ваши инструкции по обработке исключения
Здесь обработка также состоит в кратком информировании пользователя, возможно, освобождении памяти, закрытии файлов.
^ Локальная обработка
// инструкции, при выполнении которых может возникнуть ошибка
//инструкции, которые должны быть выполнены даже в случае ошибки
Эта инструкция применяется для выполнения всех необходимых действий перед передачей управления на следующий уровень обработки или глобальному обработчику. Конструкция не удаляет исключение и не обрабатывает его, а выполняет действия, которые должны быть произведены в случае ошибки, например, освобождение памяти, закрытие файла.
Конструкция try … except применяется для перехвата исключения и предоставляет возможность его обработки, глобальный обработчик не вызывается, а обработку должен обеспечить программист.
//инструкции, при выполнении которых может возникнуть ошибка
//инструкции, которые должны быть выполнены в случае ошибки
Если в инструкциях блока try возникает исключение, то управление передается первой из инструкций блока except. Если исключение не возникло, то инструкции блока except не выполняются. При появлении исключения инструкции блока except могут ликвидировать исключительную ситуацию и восстановить работоспособность программы.
procedure TForm1.btnOpenClick (Sender: TObject);
if OpenDialog1.Execute then begin
MessageDlg('Ошибка открытия таблицы', OpenDialog1.FileName, '!',
При нажатии кнопки btnOpen появляется окно выбора файла таблицы БД для открытия. После выбора файла таблицы набор данных Table1 связывается с этой таблицей и выполняется открытие набора данных. Инструкции, управляющие выбором и открытием файла, включены в блок try, так как. возможна ошибка. При ее возникновении выполняется блок except. В примере обработка заключается только в выдаче предупреждения.
Блок except можно разбить на несколько частей с помощью конструкций on … do, позволяющих анализировать класс исключения для его более удобной и полной обработки.
Если класс возникшего исключения совпадает с указанным после on, то выполняются инструкции после слова do.
Идентификатор произвольно задается пользователем и является необязательным, т.е. может отсутствовать, при этом не ставится и (:). Идентификатор – это локальная переменная, представляющая собой экземпляр класса исключения, который можно использовать для доступа к объекту возникшего исключения. Эта переменная доступна только внутри on … do.
Если в блоке except расположено несколько инструкций on . do, то else, располагающийся в конце блока, относится ко всей совокупности конструкций. Инструкции после do и else могут быть составными.
Если какие-либо действия должны быть выполнены независимо от того, произошла ошибка или нет, то удобно использовать инструкцию try. finally. Однако эта конструкция не обрабатывает исключения, а лишь смягчает их последствия. Для локальной обработки внутрь конструкции try. finally можно включить try … except.
КЭШем называется специально выделенная область оперативной памяти (буфер).
Кэшированные изменения БД заключаются в том, что на компьютере клиента в КЭШе создается локальная копия данных, и все изменения в данных выполняются в этой копии. Сделанные изменения можно подтвердить, перенеся их в основную БД, или отказаться от них. Этот механизм напоминает транзакции, но, в отличие от них, существенно снижает нагрузку на сеть, т.к. все изменения в основную БД передаются одним пакетом. Однако для всех записей локальной копии отсутствуют блокировки на изменение их значений, в то время как для основной БД, находящейся на сервере, они могут быть в этот момент установлены другим приложением.
Механизм кэшированных изменений реализуется в приложении через компоненты DataBase, Table, Query (при доступе с помощью BDE), а также через специально предназначенный для этого компонент UpdateSQL. Компонент UpdateSQL используется для подтверждения кэшированных изменений и обеспечивает модификацию наборов данных, доступных только для чтения.
Для наборов данных DataBase, Table, Query включением режима кэшированных изменений управляет свойство CachedUpdates, принимающее два значения:
True – активизирует режим;
False – выключает его (по умолчанию).
После включения режима кэширование производится автоматически. Подтверждение кэшированных изменений выполняют в два этапа:
запись кэшированных изменений в основную БД;
подтверждение или отмена сделанных изменений.
Метод ApplyUpdates набора данных записывает в БД изменения в кэшированных данных. Данные пересылаются одним пакетом, что снижает нагрузку на сеть. Однако в основной БД в текущий момент могут быть блокированные записи, поэтому потребуется обработка исключений (удобно использовать конструкцию TRY … EXCEPT).
Метод CommitUpdates набора данных подтверждает изменение.
Метод CancelUpdates отменяет их, возвращая БД в исходное состояние.
Триггеры
Пример для обработки ошибок в триггерах не сильно отличается от того, что используется в хранимых процедурах, за исключением одной маленькой детали: вы не должны использовать выражение RETURN (потому что RETURN не допускается использовать в триггерах).
С триггерами важно понимать, что они являются частью команды, которая запустила триггер, и в триггере вы находитесь внутри транзакции, даже если не используете BEGIN TRANSACTION.
Иногда я вижу на форумах людей, которые спрашивают, могут ли они написать триггер, который не откатывает в случае падения запустившую его команду. Ответ таков: нет способа сделать это надежно, поэтому не стоит даже пытаться. Если в этом есть необходимость, по возможности не следует использовать триггер вообще, а найти другое решение. Во второй и третьей частях я рассматриваю обработку ошибок в триггерах более подробно.
Читайте также: