Шрифт:
Интервал:
Закладка:
Эта стратегия работает хорошо только в случае двух условий, которые приводят к конфликтам блокировки.
* Условие 1: одна транзакция (наша транзакция) отправила на сервер изменение или удаление строки. В это время другая транзакция, стартовавшая до того, как наша транзакция заблокировала эту строку, пытается ее изменить или удалить. Другая транзакция получает конфликт блокировки и имеет два варианта выбора:
• она может отменить свою попытку и позже снова повторить ее для вновь подтвержденной версии строки;
• она может ждать, пока наша транзакция либо подтвердит, либо отменит свою работу.
* Условие 2: наша транзакция блокирует для записи целую таблицу, если имеет уровень изоляции для таблицы SNAPSHOT TABLE STABILITY или использует резервирование таблицы с помощью PROTECTED, а другая транзакция пытается изменить или удалить строку или добавить новую строку.
Предположим, что наша транзакция посылает изменение строки. Появляется другая транзакция и запрашивает изменение или удаление той же строки. При уровне изоляции SNAPSHOT и режиме WAIT другая транзакция будет ожидать, пока наша транзакция завершит свою работу путем подтверждения или отката.
Если наша транзакция подтверждает изменения, то другая транзакция получит отказ по конфликту изменения. Клиент, который запустил другую транзакцию, должен иметь обработчик исключений, который либо откатит транзакцию и заново ее запустит для повторной выдачи запроса, либо просто подтвердит транзакцию и завершит работу.
Вызов COMMIT в обработчике исключения конфликта блокировки не рекомендуется, поскольку он разрушает атомарность транзакции - некоторая работа будет завершена, некоторая нет, и впоследствии будет невозможно предсказать состояние базы данных[97].
К сожалению, Firebird имеет тенденцию объединять все исключения блокировки и сообщает о них как "deadlock" (взаимная блокировка). Нормальные случаи, которые были описаны, не являются взаимной блокировкой.
Что такое взаимная блокировка?
Взаимная блокировка является просто сокращенным названием, заимствованным из реслинга, для условия, когда две транзакции борются за изменение строк в перекрывающихся наборах и ни одна транзакция не имеет приоритета перед другой.
Например, наша транзакция имеет изменения, ожидающие завершения, для строки x и собирается изменять строку Y в то время, как другая транзакция имеет изменения, ожидающие завершения, для строки Y и собирается изменять строку x, и обе транзакции имеют режим WAIT. Как и в реслинге, взаимная блокировка может быть разрешена, только если один из борцов не удержит руку. Одна транзакция должна выполнить откат, чтобы позволить другой подтвердить свои изменения.
Firebird предоставляет приложениям средство разрешения взаимной блокировки путем сканирования блокировок каждые несколько секунд. Он произвольно выберет одну из транзакций -взаимной блокировки и выдаст для нее исключение взаимной блокировки.
Разработчики не должны бояться сообщений о взаимной блокировке. Наоборот, имеет смысл изолировать работу многих пользователей в контекстах транзакций. Вы должны допускать наличие взаимных блокировок и эффективно их обрабатывать в вашем приложении.
Если наша транзакция выбрана для разрешения взаимной блокировки, обработчик исключения в приложении должен выполнить откат транзакции, чтобы позволить другой транзакции возобновить и завершить свою работу. Альтернатива - подтверждение транзакции в обработчике исключения - не рекомендуется, поскольку наша транзакция станет неатомарной, а другая транзакция даст сбой по конфликту блокировки.
Тупиковая ситуацияВ редких случаях более двух транзакций может быть включено во взаимную блокировку при борьбе за перекрывающиеся наборы. Иногда это называется тупиковой ситуацией (программистский жаргон - deadly embrace, смертельные объятия). Сканирование блокировок выберет одну транзакцию (нашу транзакцию), ситуация будет обработана в клиентском обработчике исключений, как в предыдущем примере. При этом даже если клиент выполнит откат нашей транзакции, те другие транзакции все еще останутся заблокированными.
Активный тупикКлиент может запустить новую транзакцию и повторить попытку, однако другие соперники все еще находятся в состоянии взаимной блокировки, ожидая следующего сканирования блокировок для освобождения следующего соперника с выдачей исключения блокировки. Если приложение повторяет попытку с транзакцией WAIT, он просто ждет бесконечное время, когда будет разрешена тупиковая ситуация с другими транзакциями. Про транзакцию с такими тщетными попытками повтора говорят, что она находится в активном тупике.
Короче говоря, важно в первую очередь исключить контексты транзакций, которые могут привести к тупиковой ситуации. В качестве дополнительной защиты обработчики исключений должны быть способны быстро обрабатывать блокировки и обеспечивать, чтобы проблемные транзакции завершались чисто и без задержек.
Пора дальшеДалее мы рассмотрим транзакции с точки зрения разработчика клиентских приложений. Темы в этой главе являются нейтральными к включающим языкам. Тем не менее все современные средства разработки приложений и драйверы для Firebird используют один и тот же API в той или иной форме, что хорошо для одного, хорошо и для другого.
ГЛАВА 27. Программирование с транзакциями.
Транзакция является начальной точкой для всех взаимодействий клиентского приложения с сервером. В этой главе мы с точки зрения различных интерфейсов клиента рассмотрим запуск, управление и завершение транзакций.
Многие языки и средства разработки имеют интерфейс с Firebird. Подробное описание управления транзакциями Firebird в каждом из них находится за пределами данной книги.
Язык для транзакций
Важно обратиться к средствам реализации транзакций в Firebird. До сих пор некоторые связанные с транзакциями особенности вовсе не реализованы в динамическом SQL, а только через API. Среди небольшого количества связанных с транзакциями операторов SQL и доступных в подмножестве DSQL только COMMIT и ROLLBACK доступны в каждом интерфейсе. В книге осознанно выбран нейтральный подход к языкам средств разработки. Основной акцент делается на динамический SQL, используемый в большинстве существующих средств разработки клиентских приложений. В главе представлены некоторые проблемы, одинаково актуальные и для автора, и для читателя.
Хотя эта глава не описывает ESQL[98] или API[99], следующие разделы будут рассказывать о них в более или менее общем виде для получения некоторого понимания того, что передается через интерфейс, когда клиенты "беседуют" с серверами о транзакциях.
ESQL
Надмножество операторов SQL и подобных SQL операторов, используемых в прежние времена, даже до публикации API и подмножества DSQL, представляет синтаксис стандарта SET TRANSACTION для конфигурирования и старта транзакций. В некоторых формах он доступен в DSQL и может быть использован в утилите isql. Это удобное средство общей проверки того, как API передает эквивалентную информацию[100].
API
API предоставляет гибкий интерфейс множества сложных функций программистам С и C++ для создания клиентских приложений с наиболее тонким уровнем связи и соединения. Группа функций API реализует эквивалентные операторы SQL, относящиеся к транзакциям, например isc_start_transaction() для оператора START TRANSACTION и isc_commit_transaction() для оператора COMMIT.
Заголовочный файл API, ibase.h, объявляет прототипы функций, определения типов для каждой структуры, определения параметров и макросы, которые используются в функциях. Он поставляется в каталоге Firebird /include.
Для некоторых объектно-ориентированных сред разработки, таких как Object Pascal, Borland C++ Builder, Java, PHP, Python и DBI::Perl классы и компоненты полностью инкапсулируют вызовы API Firebird, относящиеся к транзакциям. Пользовательские драйверы для интерфейсов соединения со стандартными базами данных SQL - в особенности ODBC, JDBC и .NET- похожим образом представляют API[101].
Запуск транзакции
SQL
Оператор SQL для запуска транзакции имеет следующий синтаксис:
SET TRANSACTION [NAME <имя-транзакции>]
[READ WRITE | READ ONLY] /* режим доступа */
[WAIT | NO WAIT] /* режим разрешения блокировок */
[ISOLATION LEVEL] /* уровень изоляции */
{SNAPSHOT [TABLE STABILITY] | READ COMMITTED [[NO] RECORD VERSION]}
[RESERVING <предложение-резервирования>
| USING <дескриптор-базы-данных> [,дескриптор-базы-данных...]];
Финальное предложение RESERVING задает необязательное резервирование таблиц, обсуждавшееся в предыдущей главе. Его синтаксис представляется в виде:
<предложение-резервирования> : := <таблица> [, <та&лица> ...] [FOR [SHARED | PROTECTED] {READ | WRITE}]
[, <предложение-резервирования> [, <предложение-резервирования> ...]]
! ! !
- Delphi. Учимся на примерах - Сергей Парижский - Программирование
- Сделай видеоигру один и не свихнись - Слава Грис - Программирование / Руководства
- Психбольница в руках пациентов - Алан Купер - Программирование