Реализация неявной транзакции с использованием области транзакции. Локальные транзакцииLocal Transactions

Скачать на Телефон 06.05.2019
Скачать на Телефон

Транзакции

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

Например, рассмотрим транзакцию, которая передает $1000 со счета A на счет B. Ясно, что здесь присутствует две операции:

    снять $1000 со счета А;

    добавить $1000 на счет B.

Предположим, что приложение успешно выполнило шаг 1, но из-за какой-то ошибки шаг 2 завершился сбоем. Это ведет к несогласованию данных, поскольку общая сумма денег в системе теперь не точна. Пропало $1000.

Транзакции позволяют избежать проблем подобного рода, поскольку гарантируют, что изменения будут зафиксированы в источнике данных только в том случае, если все шаги пройдут успешно. Поэтому в рассматриваемом примере, если шаг 2 сорвется, то изменения, выполненные на шаге 1, не будут зафиксированы в базе данных. Это гарантирует, что система останется в одном из двух корректных состояний - начальном (когда деньги не переведены) или конечном (когда деньги дебетованы с одного счета и кредитованы на другой).

Транзакции характеризуются четырьмя свойствами, которые называются свойствами ACID. Здесь ACID представляет перечисленные ниже концепции:

Атомарность (Atomic)

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

Согласованность (Consistent)

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

Изолированность (Isolated)

Каждая транзакция - независимая сущность. Одна транзакция не должна влиять на другие, запущенные одновременно с ней.

Устойчивость (Durable)

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

Обратите внимание, что эти идеальные характеристики транзакции не всегда полностью достижимы. Одна из проблем связана с обеспечением изоляции. Реляционная СУБД должна блокировать данные, чтобы другие пользователи не имели к ним доступа, пока транзакция выполняется. Чем больше используется блокировок, и чем они крупнее, тем выше шансы того, что пользователь не сможет выполнить другую работу, пока транзакция находится в процессе выполнения. Другими словами, иногда приходится идти на компромисс между степенью параллелизма работы пользователей и изоляцией.

Транзакции и приложения ASP.NET

В приложениях ASP.NET можно использовать три базовых типа транзакций. Ниже представлен их список (от наименее до наиболее затратных):

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

    Инициированные клиентом (ADO.NET) транзакции . Эти транзакции управляются программно кодом вашей веб-страницы ASP.NET. "За кулисами" они применяют те же команды, что и транзакции хранимых процедур, но код использует ряд объектов ADO.NET, которые скрывают детали. Недостатком является необходимость в нескольких обращениях к базе данных, чтобы запустить и зафиксировать транзакцию.

    Транзакции COM+ . Эти транзакции обрабатываются исполняющей средой COM+ на основе декларативных атрибутов, добавленных вами в код. Транзакции COM+ используют протокол двухфазной фиксации и всегда подразумевают наличие дополнительных накладных расходов. Они требуют создания отдельного класса служебного компонента.

    В общем объекты COM+ хороший выбор только в том случае, если транзакция охватывает несколько транзакционных диспетчеров ресурсов, поскольку COM+ включает встроенную поддержку распределенных транзакций. Например, отдельная транзакция COM+ может охватывать взаимодействие с базой SQL Server и базой Oracle.

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

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

    Сохраняйте транзакции насколько возможно короткими.

    Избегайте возврата данных запросом SELECT посреди транзакции. В идеале вы должны вернуть данные перед запуском транзакции. Это сократит объем данных, заблокированных транзакцией.

    Если вы извлекаете записи, то извлекайте только те строки, которые необходимы, чтобы сократить количество блокировок.

    Где только возможно, реализуйте транзакции внутри хранимых процедур, а не используйте транзакции ADO.NET. В результате транзакции будут стартовать и завершаться быстрее, т.к. серверу базы данных не придется взаимодействовать с клиентом (веб-приложением).

    Избегайте транзакций, которые комбинируют множество независимых пакетов работы. Помещайте отдельные пакеты в отдельные транзакции.

    По возможности избегайте обновлений, которые затрагивают большое количество записей.

В ADO.NET также поддерживается высокоуровневая модель распространяемых транзакций. Однако распространяемая транзакция - это не какой-то новый тип транзакции, а просто способ создания инициируемой клиентом транзакции, которая при необходимости может автоматически расширяться до транзакции COM+. Вы не должны использовать распространяемые транзакции, если только действительно в них не нуждаетесь, потому что трудно предсказать их влияние на производительность и масштабируемость окончательных решений.

Запомните эмпирическое правило: применяйте транзакции только тогда, когда операция того требует. Например, если вы просто выбираете записи из базы данных либо инициируете запрос, то транзакции не нужны. С другой стороны, если вы вставляете запись Order, которая связана с последовательностью зависимых записей OrderItem, то транзакция может понадобиться. Вообще транзакции никогда не требуются для одиночных команд, таких как индивидуальные операторы UPDATE, DELETE или INSERT, поскольку они по определению транзакционны.

Транзакции хранимых процедур

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

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

Код хранимых процедур варьируется в зависимости от используемой базы данных, но большинство реляционных СУБД поддерживают SQL-оператор BEGIN TRANSACTION . После запуска транзакции все последующие операторы рассматриваются как ее часть. Завершается транзакция с помощью оператора COMMIT или ROLLBACK . Если этого не сделать, транзакция будет автоматически отменена.

Ниже показан пример псевдокода, выполняющий перевод средств между счетами. Это упрощенная версия, в которой допускается установка для счета отрицательного баланса:

CREATE Procedure TransferAmount (@Amount Money @ID_A int @ID_B int) AS BEGIN TRANSACTION UPDATE Accounts SET Balance = Balance + @Amount WHERE AccountID = @ID_A IF (@@ERROR > 0) GOTO PROBLEM UPDATE Accounts SET Balance = Balance - @Amount WHERE AccountID = @ID_B IF (@@ERROR > 0) GOTO PROBLEM -- Никаких проблем обнаружено не было COMMIT RETURN -- Код для обработки ошибок PROBLEM: ROLLBACK; RAISERROR("Обновление выполнить не удалось", 16, 1)

В приведенном примере применяются средства ограниченной обработки ошибок Transact-SQL (вариант SQL, реализованный в SQL Server). При использовании значения @@ERROR в Transact-SQL следует соблюдать осторожность и проверять его немедленно после каждой операции. Это связано с тем, что @@ERROR сбрасывается в 0 при успешном завершении оператора SQL. В результате, если первое обновление потерпит неудачу, а второе выполнится успешно, @@ERROR вернет 0. В этой точке проверять его уже слишком поздно.

Инициированные клиентом транзакции ADO.NET

Большинство поставщиков данных ADO.NET включают поддержку баз данных. Транзакции стартуют через объект Connection вызовом метода BeginTransaction() . Этот метод возвращает специфичный для поставщика объект Transaction , используемый для управления транзакцией. Все классы Transaction реализуют интерфейс IDbTransaction . Поставщики включают классы наподобие SqlTransaction, OleDbTransaction, OracleTransaction и т.д. реализующие этот интерфейс.

Класс Transaction предоставляет два ключевых метода:

Commit()

Этот метод указывает завершение транзакции и помещение выполненных изменений в источнике данных.

Rollback()

Этот метод указывает отмену транзакции. Неоконченные изменения отменяются, и состояние базы остается прежним.

Обычно метод Commit() используется в конце операции. Однако если в процессе возникнет любое исключение, должен быть вызван Rollback(). Рассмотрим пример вставки двух записей в таблицу Employees:

Protected void Page_Load(object sender, EventArgs e) { string connectionString = WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString; SqlConnection con = new SqlConnection(connectionString); SqlCommand cmd1 = new SqlCommand("INSERT INTO Employees (LastName, FirstName) VALUES ("Petrov", "Vasya")", con); SqlCommand cmd2 = new SqlCommand("INSERT INTO Employees (LastName, FirstName) VALUES ("Ivanov", "Vadim")", con); SqlTransaction transaction = null; try { // Открыть соединение и создать транзакцию con.Open(); transaction = con.BeginTransaction(); // Включить в транзакцию две команды cmd1.Transaction = transaction; cmd2.Transaction = transaction; // Выполнить обе команды cmd1.ExecuteNonQuery(); cmd2.ExecuteNonQuery(); // Зафиксировать транзакцию transaction.Commit(); } catch { // В случае ошибки отменить транзакцию transaction.Rollback(); } finally { // В любом случае закрыть соединение с базой данных con.Close(); } }

Обратите внимание, что просто создать и зафиксировать транзакцию недостаточно. Также необходимо явно включить каждый объект Command в состав транзакции, установив значение их свойства Command.Transaction равным объекту Transaction. Если вы попытаетесь выполнить команду, которая не является частью текущей транзакции, пока транзакция находится в процессе выполнения, то получите ошибку. Однако в будущем эта объектная модель может разрешить поддержку более одной транзакции одновременно в пределах одного соединения.

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

Чтобы протестировать свойство отката (отмены) транзакции, вставьте следующую строку непосредственно перед вызовом метода Commit() в предыдущем примере:

Throw new ApplicationException();

Эта строка генерирует исключение, которое приведет к откату транзакции, гарантируя, что ни одна запись не попадет в базу данных.

Хотя транзакции ADO.NET вращаются вокруг объектов Command и Transaction, лежащие в основе команды не отличаются от тех, что применяются в транзакциях хранимых процедур. Например, когда вызывается метод BeginTransaction() с поставщиком данных SQL Server, он отправляет базе данных команду BEGIN TRANSACTION.

Транзакция должна быть завершена как можно скорее (запущена как можно позже и завершена как можно раньше). К тому же активная транзакция блокирует различные задействованные ресурсы, поэтому следует выбирать только те строки таблиц, которые действительно необходимы.

Уровни изоляции

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

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

Исключительные блокировки - это блокировки, которые предотвращают одновременную модификацию данных двумя или более транзакциями. Исключительная блокировка устанавливается, когда транзакция собирается обновить данные, и никакой другой блокировки на этот момент не установлен. Ни один другой пользователь не сможет модифицировать данные, пока на них установлена исключительная блокировка.

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

В хранимых процедурах SQL Server можно устанавливать уровни изоляции, используя команду SET TRANSACTION ISOLATION LEVEL . В ADO.NET можно передать значение перечисления IsolationLevel перегруженному методу Connection.BeginTransaction(). В таблице ниже показаны его возможные значения:

Значения перечисления IsolationLevel
Значение Описание
ReadUncommitted

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

ReadCommitted

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

Snapshot

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

RepeatableRead

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

Serializable

Блокировка диапазона, устанавливаемая на используемые данные, предотвращает обновление и вставку другими пользователями записей, которые могут попасть в диапазон. Это единственный уровень изоляции, который исключает возможность фантомных строк. Однако оказывает исключительно негативное влияние на возможность параллельной работы пользователей, и редко используется в сценариях работы со многими пользователями

В этой таблице введена терминология, связанная с базами данных, которая требует пояснений:

Грязное чтение (dirty read)

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

Невоспроизводимое чтение (unrepeatable read)

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

Фантомная строка (phantom row)

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

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

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

Точки сохранения

Всякий раз, когда производится откат транзакции, аннулируются результаты всех команд, выполненных с момента старта транзакции. Но как быть, если вы хотите откатить только часть текущей транзакции? В SQL Server для этого предназначено средство, которое называется точками сохранения (savepoints) .

Точки сохранения - это метки, которые работают подобно книжным закладкам. Вы отмечаете определенную точку в потоке транзакции, и затем можете выполнять ее откат до этой точки. Точка сохранения устанавливается с помощью метода Transaction.Save() . Обратите внимание, что метод Save() доступен только в классе SqlTransaction, поскольку не является частью стандартного интерфейса IDbTransaction.

Ниже демонстрируется концепция использования точки сохранения:

// Запустить транзакцию SqlTransaction tran = con.BeginTransaction(); // (Включить и выполнить некоторые команды в транзакции.) // Отметить точку сохранения tran.Save("CompletedInsert") ; // (Включить и выполнить еще какие-то команды в транзакции.) // Если необходимо, выполнить откат до точки сохранения tran.Rollback("CompletedInsert"); // Зафиксировать или откатить всю транзакцию tran.Commit();

Обратите внимание на использование метода Rollback() с точкой сохранения в качестве параметра. Если вы хотите откатить всю транзакцию, просто пропустите этот параметр. При откате транзакции до точки сохранения все точки сохранения, определенные после данной, теряются. Если они нужны, их придется установить заново.

Транзакцией называется выполнение последовательности команд (SQL-конструкций) в базе данных, которая либо фиксируется при успешной реализации каждой команды, либо отменяется при неудачном выполнении хотя бы одной команды. Большинство современных СУБД поддерживают механизм транзакций, и подавляющее большинство клиентских приложений, работающих с ними, используют для выполнения своих команд транзакции.

Возникает вопрос - зачем нужны транзакции? Представим себе, что в базу данных BDTur_firm2 требуется вставить связанные записи в две таблицы - «Туристы» и «Информация о туристах». Если запись, вставляемая в таблицу «Туристы», окажется неверной, например, из-за неправильно указанного кода туриста, база данных не позволит внести изменения, но при этом в таблице «Информация о туристах» появится ненужная запись. Рассмотрим такую ситуацию на примере.

Запустим Management Studio, в новом бланке введем запрос для добавления двух записей:




VALUES (8, "CA 1234567", "Новосибирск", "Россия", 1234567, 996548);

Две записи успешно добавляются в базу данных:

(1 row(s) affected) //или (строк обработано: 1)
(1 row(s) affected) //или (строк обработано: 1)

Теперь спровоцируем ошибку - изменим код туриста только во втором запросе:

INSERT INTO Туристы ([Код туриста], Фамилия, Имя, Отчество)
VALUES (8, "Тихомиров", "Андрей", "Борисович");
INSERT INTO [Информация о туристах]([Код туриста], [Серия паспорта], Город, Страна, Телефон, Индекс)
VALUES (9, "CA 1234567", "Новосибирск", "Россия", 1234567, 996548);

Появляется сообщение о невозможности вставки первой записи с уже имеющимся значением ключевого поля. Вторая запись, однако, добавляется в таблицу:

Сообщение 2627, уровень 14, состояние 1, строка 1

The statement has been terminated.
(строк обработано: 1)

Извлечем содержимое обеих таблиц следующим двойным запросом:

SELECT * FROM Туристы
SELECT * FROM [Информация о туристах]

В таблице «Информация о туристах» последняя запись добавилась безо всякой связи с записью таблицы «Туристы» (рис. 99). Для того чтобы избегать подобных ошибок, нужно применить транзакцию.

Рис. 99. Содержимое таблиц «Туристы» и «Информация о туристах» - нарушение связи

Удалим все внесенные записи из обеих таблиц и оформим исходные SQL-конструкции в виде транзакции:

BEGIN TRAN

INSERT INTO Туристы ([Код туриста], Фамилия, Имя, Отчество)
VALUES (8, "Тихомиров", "Андрей", "Борисович");


Город, Страна, Телефон, Индекс)
VALUES (8, "CA 1234567", "Новосибирск", "Россия", 1234567, 996548); SELECT @OshibkiTabliciInfoTourists=@@ERROR IF @OshibkiTabliciTourists=0 AND @OshibkiTabliciInfoTourists=0 COMMIT TRAN ELSE
ROLLBACK TRAN

Начало транзакции объявляется с помощью команды BEGIN TRAN. Далее создаются два параметра - @OshibkiTabliciTourists, OshibkiTabliciInfoTourists для сбора ошибок. После первого запроса возвращаем значение, которое встроенная функция @@ERROR присваивает первому параметру:

SELECT @OshibkiTabliciTourists=@@ERROR

То же самое делаем после второго запроса для другого параметра:

SELECT @OshibkiTabliciInfoTourists=@@ERROR

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

IF @OshibkiTabliciTourists=0 AND @OshibkiTabliciInfoTourists=0

В этом случае подтверждаем транзакцию (в данном случае внесение изменений) при помощи команды COMMIT TRAN. В противном случае - если значение хотя бы одного из параметров @OshibkiTabliciTourists и @OshibkiTabliciInfoTourists оказывается отличным от нуля, отменяем транзакцию при помощи команды ROLLBACK TRAN.

После выполнения транзакции появляется сообщение о добавлении двух строк:

(строк обработано: 1)
(строк обработано: 1)

Снова изменим код туриста во втором запросе:

BEGIN TRAN
DECLARE @OshibkiTabliciTourists int, @OshibkiTabliciInfoTourists int
INSERT INTO Туристы ([Код туриста], Фамилия, Имя, Отчество)
VALUES (8, "Тихомиров", "Андрей", "Борисович");
SELECT @OshibkiTabliciTourists=@@ERROR
INSERT INTO [Информация о туристах]([Код туриста], [Серия паспорта],
Город, Страна, Телефон, Индекс)
VALUES (9, "CA 1234567", "Новосибирск", "Россия", 1234567, 996548);
SELECT @OshibkiTabliciInfoTourists=@@ERROR
IF @OshibkiTabliciTourists=0 AND @OshibkiTabliciInfoTourists=0
COMMIT TRAN
ELSE
ROLLBACK TRAN

Запускаем транзакцию - появляется в точности такое же сообщение, что и в случае применения обычных запросов:

Сообщение 2627, уровень 14, состояние 1, строка 3
Violation of PRIMARY KEY constraint ‘PK_Туристы". Cannot insert duplicate key in object ‘dbo.Туристы".
The statement has been terminated. (строк обработано: 1)

Однако теперь изменения не были внесены во вторую таблицу (рис. 100).

Рис. 100. Содержимое таблиц «Туристы» и «Информация о туристах» после выполнения неудачной транзакции

Сообщение (1 row(s) affected), указывающее на «добавление» одной записи, в данном случае оно всего лишь означает, что вторая SQL-конструкция была верной, и запись могла быть добавлена в случае успешного выполнения транзакции.

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

Транзакции в ADO . NET

Перейдем теперь к рассмотрению транзакций в ADO .NET. Создадим новое консольное приложение Easy Transaction. Поставим задачу: передать те же самые данные в две таблицы - «Туристы» и «Информация о туристах».

Полный листинг данного приложения выглядит следующим образом:

using System;
using System.Data.SqlClient;
namespace EasyTransaction {
class Class1 {
static void Main(string args)
{
//Создаем соединение
SqlConnection conn = new SqlConnection();
conn.ConnectionString = @"Data Source=.\SQLEXPRESS;AttachDbFilename=" +
@"D:\ВМИ\For ADO\BDTur_firmSQL2.mdf" +
"integrated Security=True;Connect Timeout=30;User Instance=True";
conn.Open();
SqlCommand myCommand = conn.CreateCommand();
//Создаем транзакцию
myCommand.Transaction = conn.BeginTransaction(System.Data.IsolationLevel.Serializable);
try {
myCommand.CommandText =
"INSERT INTO Туристы ([Код туриста], Фамилия, Имя, Отчество) " +
"VALUES (9, "Тихомиров", "Андрей", "Борисович")";
myCommand.CommandText = "INSERT INTO [Информация о туристах]" +
" ([Код туриста], [Серия паспорта], Город, Страна, Телефон, Индекс) " +
"VALUES (9, "CA 1234567", "Новосибирск", "Россия", 1234567, 996548)";
myCommand.ExecuteNonQuery();
//Подтверждаем транзакцию
Console.WriteLine("Передача данных успешно завершена");
}
catch(Exception ex) {
//Отклоняем транзакцию
Console.WriteLine("При передаче данных произошла ошибка: "+ ex.Message);
}
finally {
conn.Close();
}
} end Main
} end Class
} end namespace

Перед запуском приложения снова удаляем все добавленные записи из таблиц. При успешном выполнении запроса появляется соответствующее сообщение, а в таблицы добавляются записи (рис. 101).

Рис. 101. Приложение EasyTransaction. Транзакция выполнена

Повторный запуск этого приложения приводит к отклонению транзакции - нельзя вставлять записи с одинаковыми значениями первичных ключей (рис. 102).

Рис. 102. Приложение EasyTransaction. Транзакция отклонена

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

//Создаем соединение
... см. в примере приложения EasyTransaction
//Создаем транзакцию
myCommand.Transaction = conn.BeginTransaction();
try {
//Выполняем команды, вызываем одну или несколько хранимых процедур
//Подтверждаем транзакцию
myCommand.Transaction.Commit();
}
catch(Exception ex) {
//Отклоняем транзакцию
myCommand.Transaction.Rollback();
}
finally {
//Закрываем соединение
conn.Close();
}

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

1. Dirty reads - «грязное» чтение. Первый пользователь начинает транзакцию, изменяющую данные. В это время другой пользователь (или создаваемая им транзакция) извлекает частично измененные данные, которые не являются корректными.

2. Non-repeatable reads - неповторяемое чтение. Первый пользователь начинает транзакцию, изменяющую данные. В это время другой пользователь начинает и завершает другую транзакцию. Первый пользователь при повторном чтении данных (например, если в его транзакцию входит несколько инструкций SELECT) получает другой набор записей.

3. Phantom reads - чтение фантомов. Первый пользователь начинает транзакцию, выбирающую данные из таблицы. В это время другой пользователь начинает и завершает транзакцию, вставляющую или удаляющую записи. Первый пользователь получит другой набор данных, содержащий фантомы - удаленные или измененные строки.

Для решения этих проблем разработаны четыре уровня изоляции транзакции:

1. Read uncommitted. Транзакция может считывать данные, с которыми работают другие транзакции. Применение этого уровня изоляции может привести ко всем перечисленным проблемам.

2. Read committed. Транзакция не может считывать данные, с которыми работают другие транзакции. Применение этого уровня изоляции исключает проблему «грязного» чтения.

3. Repeatable read. Транзакция не может считывать данные, с которыми работают другие транзакции. Другие транзакции также не могут считывать данные, с которыми работает эта транзакция. Применение этого уровня изоляции исключает все проблемы, кроме чтения фантомов.

4. Serializable. Транзакция полностью изолирована от других транзакций. Применение этого уровня изоляции полностью исключает все проблемы.

По умолчанию установлен уровень Read committed. В справке Microsoft SQL Server 2005 приводится таблица, иллюстрирующая различные уровни изоляции (рис. 103).

Рис. 103. Уровни изоляции Microsoft SQL Server 2005

Использование наибольшего уровня изоляции (Serializable) означает наибольшую безопасность и вместе с тем наименьшую производительность - все транзакции выполняются в виде серии, последующая вынуждена ждать завершения предыдущей. И наоборот, применение наименьшего уровня (Read uncommitted) означает максимальную производительность и полное отсутствие безопасности. Впрочем, нельзя дать универсальных рекомендаций по применению этих уровней - в каждой конкретной ситуации решение будет зависеть от структуры базы данных и характера выполняемых запросов.

Для установки уровня изоляции применяется следующая команда:

SET TRANSACTION ISOLATION LEVEL
READ UNCOMMITTED
или READ COMMITTED
или REPEATABLE READ
или SERIALIZABLE

Например, в транзакции, добавляющей две записи, уровень изоляции указывается следующим образом:

BEGIN TRAN SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
DECLARE @OshibkiTabliciTourists int, @OshibkiTabliciInfoTourists int
...
ROLLBACK TRAN

В ADO .NET уровень изоляции можно установить при создании транзакции:

myCommand.Transaction = conn.BeginTransaction(System.Data.IsolationLevel.Serializable); Дополнительно поддерживаются еще два уровня (рис. 104):

1. Chaos. Транзакция не может перезаписать другие непринятые транзакции с большим уровнем изоляции, но может перезаписать изменения, внесенные без использования транзакций. Данные, с которыми работает текущая транзакция, не блокируются;

2. Unspecified. Отдельный уровень изоляции, который может применяться, но не может быть определен. Транзакция с этим уровнем может применяться для задания собственного уровня изоляции.

Рис. 104. Определение уровня транзакции

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

Локальные транзакции Local Transactions

В этой статье

В ADO.NET ADO.NET транзакции используются для связи нескольких задач, чтобы они выполнялись как одно целое. Transactions in ADO.NET ADO.NET are used when you want to bind multiple tasks together so that they execute as a single unit of work. Например, пусть приложение выполняет две задачи. For example, imagine that an application performs two tasks. Во-первых, оно заносит в таблицу сведения о заказе. First, it updates a table with order information. Во-вторых, обновляет таблицу, содержащую список товаров на складе, списывая заказанные элементы. Second, it updates a table that contains inventory information, debiting the items ordered. При сбое любой задачи, затем оба обновления откатываются. If either task fails, then both updates are rolled back.

Определение типа транзакции Determining the Transaction Type

Транзакция считается локальной транзакции при состоит из одной фазы и обрабатывается непосредственно базой данных. A transaction is considered to be a local transaction when it is a single-phase transaction and is handled by the database directly. Транзакция считается распределенной транзакции при координируется монитором транзакций и используется для разрешения транзакций резервные механизмы (например, двухфазную фиксацию). A transaction is considered to be a distributed transaction when it is coordinated by a transaction monitor and uses fail-safe mechanisms (such as two-phase commit) for transaction resolution.

Для выполнения локальных транзакций каждый поставщик данных платформы .NET Framework .NET Framework имеет свой собственный объект Transaction . Each of the .NET Framework .NET Framework data providers has its own Transaction object for performing local transactions. Если требуется выполнить транзакцию в базе данных SQL Server, выбирается транзакция System.Data.SqlClient . If you require a transaction to be performed in a SQL Server database, select a System.Data.SqlClient transaction. Для транзакции Oracle используйте поставщик System.Data.OracleClient . For an Oracle transaction, use the System.Data.OracleClient provider. Кроме того, DbTransaction класс, который доступен для написания независимого от поставщика кода, который требуется транзакций. In addition, there is a DbTransaction class that is available for writing provider-independent code that requires transactions.

Примечание

Транзакции наиболее эффективны, если они выполняются на сервере. Transactions are most efficient when they are performed on the server. При работе с базой данных SQL Server, интенсивно использующей явные транзакции, следует рассмотреть возможность их записи в виде хранимых процедур при помощи инструкции Transact-SQL BEGIN TRANSACTION. If you are working with a SQL Server database that makes extensive use of explicit transactions, consider writing them as stored procedures using the Transact-SQL BEGIN TRANSACTION statement.

Выполнение транзакций с использованием одного соединения Performing a Transaction Using a Single Connection

В ADO.NET ADO.NET транзакции управляются объектом Connection . In ADO.NET ADO.NET , you control transactions with the Connection object. Инициировать транзакцию можно с помощью метода BeginTransaction . You can initiate a local transaction with the BeginTransaction method. После начала транзакции при помощи свойства Transaction объекта Command к ней можно прикрепить команду. Once you have begun a transaction, you can enlist a command in that transaction with the Transaction property of a Command object. Затем в зависимости от успеха или ошибки компонентов транзакции можно зафиксировать или откатить изменения, сделанные в источнике данных. You can then commit or roll back modifications made at the data source based on the success or failure of the components of the transaction.

Примечание

Метод EnlistDistributedTransaction не должен использоваться для локальной транзакции. The EnlistDistributedTransaction method should not be used for a local transaction.

Область действия транзакции ограничена соединением. The scope of the transaction is limited to the connection. В следующем примере выполняется явная транзакция, состоящая из двух отдельных команд в блоке try . The following example performs an explicit transaction that consists of two separate commands in the try block. Команды выполняют инструкции INSERT для таблицы Production.ScrapReason в образце базы данных AdventureWorks в SQL Server, которые будут зафиксированы при отсутствии исключений. The commands execute INSERT statements against the Production.ScrapReason table in the AdventureWorks SQL Server sample database, which are committed if no exceptions are thrown. При возникновении исключения код в блоке catch произведет откат транзакции. The code in the catch block rolls back the transaction if an exception is thrown. При отмене транзакции или обрыве соединения до выполнения транзакции она откатывается автоматически. If the transaction is aborted or the connection is closed before the transaction has completed, it is automatically rolled back.

Пример Example

Чтобы осуществить транзакцию, выполните указанные ниже действия. Follow these steps to perform a transaction.

    Вызовите метод BeginTransaction объекта SqlConnection для отметки начала транзакции. Call the BeginTransaction method of the SqlConnection object to mark the start of the transaction. Метод BeginTransaction возвращает ссылку на транзакцию. The BeginTransaction method returns a reference to the transaction. Эта ссылка назначается объектам SqlCommand , прикрепленным к транзакции. This reference is assigned to the SqlCommand objects that are enlisted in the transaction.

    Присвойте объект Transaction свойству Transaction объекта SqlCommand . Assign the Transaction object to the Transaction property of the SqlCommand to be executed. Исключение вызывается, если команда выполняется при соединении с активной транзакцией, а объект Transaction не был назначен свойству Transaction объекта Command . If a command is executed on a connection with an active transaction, and the Transaction object has not been assigned to the Transaction property of the Command object, an exception is thrown.

    Выполните требуемые команды. Execute the required commands.

    Для выполнения транзакции вызовите метод Commit объекта SqlTransaction , для завершения транзакции вызовите метод Rollback . Call the Commit method of the SqlTransaction object to complete the transaction, or call the Rollback method to end the transaction. Транзакция откатывается, если соединение закрывается или пропадает до выполнения метода Commit либо Rollback . If the connection is closed or disposed before either the Commit or Rollback methods have been executed, the transaction is rolled back.

Следующий пример кода демонстрирует транзакционную логику, используемую ADO.NET ADO.NET с Microsoft SQL Server. The following code example demonstrates transactional logic using ADO.NET ADO.NET with Microsoft SQL Server.

Using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); // Start a local transaction. SqlTransaction sqlTran = connection.BeginTransaction(); // Enlist a command in the current transaction. SqlCommand command = connection.CreateCommand(); command.Transaction = sqlTran; try { // Execute two separate commands. command.CommandText = "INSERT INTO Production.ScrapReason(Name) VALUES("Wrong size")"; command.ExecuteNonQuery(); command.CommandText = "INSERT INTO Production.ScrapReason(Name) VALUES("Wrong color")"; command.ExecuteNonQuery(); // Commit the transaction. sqlTran.Commit(); Console.WriteLine("Both records were written to database."); } catch (Exception ex) { // Handle the exception if the transaction fails to commit. Console.WriteLine(ex.Message); try { // Attempt to roll back the transaction. sqlTran.Rollback(); } catch (Exception exRollback) { // Throws an InvalidOperationException if the connection // is closed or the transaction has already been rolled // back on the server. Console.WriteLine(exRollback.Message); } } } Using connection As New SqlConnection(connectionString) connection.Open() " Start a local transaction. Dim sqlTran As SqlTransaction = connection.BeginTransaction() " Enlist a command in the current transaction. Dim command As SqlCommand = connection.CreateCommand() command.Transaction = sqlTran Try " Execute two separate commands. command.CommandText = _ "INSERT INTO Production.ScrapReason(Name) VALUES("Wrong size")" command.ExecuteNonQuery() command.CommandText = _ "INSERT INTO Production.ScrapReason(Name) VALUES("Wrong color")" command.ExecuteNonQuery() " Commit the transaction sqlTran.Commit() Console.WriteLine("Both records were written to database.") Catch ex As Exception " Handle the exception if the transaction fails to commit. Console.WriteLine(ex.Message) Try " Attempt to roll back the transaction. sqlTran.Rollback() Catch exRollback As Exception " Throws an InvalidOperationException if the connection " is closed or the transaction has already been rolled " back on the server. Console.WriteLine(exRollback.Message) End Try End Try End Using

См. также See also

  • Транзакции и параллельность Transactions and Concurrency
  • Распределенные транзакции Distributed Transactions
  • Интеграция System.Transactions с SQL Server System.Transactions Integration with SQL Server
  • Центр разработчиков наборов данных и управляемых поставщиков ADO.NET ADO.NET Managed Providers and DataSet Developer Center

Введение

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

У транзакции есть начало и конец, определяющие ее границы (transaction boundaries), внутри которых транзакция может охватывать различные процессы и компьютеры. Все ресурсы, используемые в ходе данной транзакции, считаются участвующими в этой транзакции. Для поддержания целостности используемых ресурсов транзакция должна обладать свойствами ACID: Atomicity (атомарность), Consistency (целостность), Isolation (изоляция) и Durability (отказоустойчивость). Подробнее об основах обработки транзакций см. Processing Transactions (EN) в Microsoft .NET Framework SDK и Transaction Processing в Microsoft Platform SDK.

В этой статье мы покажем, как выполнять локальные и распределенные транзакции в приложениях Microsoft .NET.

Локальные и распределенные транзакции

Локальной называется транзакция, областью действия которой является один ресурс, поддерживающий транзакции, - база данных Microsoft ® SQL Server™, очередь сообщений MSMQ и др. Например, отдельно взятая СУБД может вводить в действие правила ACID, когда в ее распоряжении имеются все данные, участвующие в транзакции. В SQL Server предусмотрен внутренний диспетчер транзакций (transaction manager), предоставляющий функциональность для фиксации (commit) и отката (rollback) транзакций.

Распределенные транзакции могут использовать гетерогенные ресурсы, поддерживающие транзакции, включать самые разнообразные операции, например выборку информации из базы данных SQL Server, считывание сообщений Message Queue Server и запись в другие базы данных. Программирование распределенных приложений упрощается программным обеспечением, способным координировать фиксацию и откат, а также восстановление данных, хранящихся в различных ресурсах. Одной из таких технологий является DTC (Microsoft Distributed Transaction Coordinator). DTC реализует протокол двухфазной фиксации (two-phase commit protocol), гарантирующий непротиворечивость результатов транзакции во всех ресурсах, участвующих в этой транзакции. DTC поддерживает только приложения, в которых реализуются совместимые с ним интерфейсы управления транзакциями. Эти приложения называются диспетчерами ресурсов (Resource Managers) (дополнительную информацию по этой теме см. в Distributed Transactions (EN) в.NET Framework Developer’s Guide). В настоящее время существует довольно много таких приложений - MSMQ, Microsoft SQL Server, Oracle, Sybase и др.

Транзакции баз данных

Вызов хранимой процедуры (stored procedure), которая заключает необходимые операции в операторы BEGIN TRANSACTION и COMMIT/ROLLBACK TRANSACTION, дает наилучшую производительность, позволяя выполнить транзакцию с разовым обменом данными с сервером (single round-trip) . Кроме того, транзакции баз данных могут быть вложенными, т. е. внутри активной транзакции можно начать выполнение новой транзакции.

В следующем фрагменте кода оператор BEGIN TRANSACTION начинает новую транзакцию. Транзакцию можно завершить двумя способами: либо фиксацией изменений в базе данных оператором COMMIT TRANSACTION, либо (при возникновении какой-либо ошибки) отменой всех изменений оператором ROLLBACK TRANSACTION.

CREATE PROCEDURE Proc1 … AS -- Начинаем транзакцию BEGIN TRANSACTION -- Выполняем операции транзакции … -- Проверяем наличие ошибок If @@Error <> 0 -- Откатываем транзакцию ROLLBACK TRANSACTION … -- Фиксируем транзакцию COMMIT TRANSACTION

Показанная ниже хранимая процедура принимает в качестве входного параметра XML-представление информации о заказе (order). Для выполнения соответствующих вставок в таблицы Orders и OrderDetails хранимая процедура загружает и анализирует XML с помощью системной хранимой процедуры sp_xmlpreparedocument . Как видно из исходного кода, все операции хранимой процедуры включены в явно выполняемую транзакцию, поэтому при неудачном завершении любой операции все внесенные изменения отменяются (откатываются).

Заметьте, что процедура устанавливает флаг XACT_ABORT в ON, указывая, что SQL Server должен автоматически откатить транзакцию, если выполнить какой-нибудь оператор не удастся.

CREATE PROCEDURE InsertOrder @Order NVARCHAR(4000) = NULL , @OrderId int Output AS SET NOCOUNT ON DECLARE @hDoc INT DECLARE @PKId INT -- Указываем, что SQL Server должен автоматически откатывать текущую -- транзакцию, если оператор Transact-SQL генерирует ошибку периода -- выполнения (run-time error). SET XACT_ABORT ON -- Начинаем транзакцию BEGIN TRANSACTION -- Загружаем и анализируем содержимое входного XML-представления -- информации о заказе, а затем помещаем его в XMLDocument EXEC sp_xml_preparedocument @hDoc OUTPUT, @Order -- Выбираем заголовок заказа из XMLDocument-узла Orders -- и вставляем его в таблицу Orders INSERT Orders(CustomerId, OrderDate, ShipToName, ShipToAddressId, OrderStatus) SELECT CustomerId, CONVERT(DateTime,OrderDate), ShipToName, ShipToAddressId, OrderStatus FROM OPENXML(@hDoc, "/NewDataSet/Orders") WITH (CustomerId int "CustomerId", OrderDate nvarchar(23) "OrderDate", ShipToName nvarchar(40) "ShipToName", ShipToAddressId int "ShipToAddressId", OrderStatus int "OrderStatus") -- Выбираем OrderId заказа, только что вставленного в таблицу Orders -- для использования при вставке позиций заказа (order details) SELECT @PKId = @@IDENTITY -- Выбираем позиции заказа из XMLDocument-узла Details -- и вставляем их в таблицу OrderDetails INSERT OrderDetails (OrderId, ItemId, UnitPrice, Quantity) SELECT @PKId as OrderId, ItemId, UnitPrice, Quantity FROM OPENXML(@hDoc, "/NewDataSet/Details") WITH (ItemId int "ItemId", UnitPrice money "UnitPrice", Quantity int "Quantity") -- Присваиваем значение выходному параметру Select @OrderId = @PKId -- Фиксируем транзакцию COMMIT TRANSACTION EXEC sp_xml_removedocument @hDoc RETURN 0 GO

Хотя такой подход обеспечивает хорошую производительность, при его использовании приходится программировать на Transact SQL, а это сложнее, чем на языке, совместимом с.NET.

Транзакции вручную

Транзакции, выполняемые вручную (manual transactions), позволяют явно управлять границами транзакции с помощью команд начала и конца транзакции. В этой модели также поддерживаются вложенные транзакции, т. е. вы можете начинать новую транзакцию в рамках активной транзакции. Расплата за возможность такого управления заключается в том, что на вас ложится бремя включения в транзакцию нужных ресурсов данных и их координация. Встроенной поддержки распределенных транзакций нет, поэтому управление распределенной транзакцией вручную потребует взять на себя массу обязанностей. Вам придется управлять каждым подключением к ресурсу и его использованием, а также реализовать поддержку свойств ACID в транзакции.

Транзакции ADO.NET, выполняемые вручную

Транзакции вручную поддерживают оба провайдера данных Microsoft ADO.NET, которые предоставляют набор объектов, позволяющих создавать соединение с хранилищем данных, начинать транзакцию, фиксировать или откатывать ее и, наконец, закрывать соединение. В своих примерах мы будем использовать управляемый ADO. NET-провайдер SQL (ADO.NET SQL managed provider).

Для выполнения операций в рамках единой транзакции нужно создать объект SQLTransaction , начать транзакцию с помощью объекта SQLConnection , добиться, чтобы все операции над базой данных проходили в этой транзакции, и зафиксировать или отменить транзакцию. Объект SQLTransaction предоставляет целый ряд свойств и методов для управления транзакцией. При успешном выполнении всех операций транзакции вы можете зафиксировать изменения в базе данных методом Commit . Для отката изменений применяется метод Rollback объекта SQLTransaction .

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

Visual Basic .NET

Dim conn as SQLConnection Dim cmd as SQLCommand Dim txn As SQLTransaction conn = New SQLConnection("ConnString") cmd = New SQLCommand " Открываем соединение conn.Open() " Начинаем транзакцию txn = conn.BeginTransaction() cmd.Transaction = Txn

Visual C# .NET

SQLConnection Conn = New SQLConnection("ConnString"); SQLCommand Cmd = New SQLCommand; // Открываем соединение Conn.Open(); // Начинаем транзакцию SQLTransaction Txn = Conn.BeginTransaction(); // Настраиваем свойство Transaction на транзакцию, где выполняется // SQL-команда Cmd.Transaction = Txn;

В примере, представленном ниже, мы выполняем в рамках транзакции две SQL-команды. Первая вставляет заголовок заказа (order header) в таблицу Orders и возвращает OrderId только что созданного заказа. Этот OrderId используется во второй команде, которая вставляет позиции этого заказа в таблицу OrderDetails. Транзакция отменяется, если хотя бы одна из двух команд терпит неудачу; при этом строки в базу данных не добавляются.

Visual Basic .NET

Dim conn As SqlConnection Dim cmd As SqlCommand Dim tran As SqlTransaction " Создаем новое соединение conn = New SqlConnection("ConnString") " Открываем соединение conn.Open() " Создаем объект Command cmd = New SqlCommand() " Создаем транзакцию tran = conn.BeginTransaction " Настраиваем свойство Transaction на транзакцию, где выполняется " SQL-команда cmd.Transaction = tran Try " Вставляем заголовок заказа. " Настраиваем свойства Command With cmd .CommandType = CommandType.StoredProcedure .CommandText = "InsertOrderHeader" .Connection = conn " Добавляем входные и выходные параметры.Parameters.Add("@CustomerId", SqlDbType.Int) .Parameters("@CustomerId").Direction = ParameterDirection.Input … " Устанавливаем значения параметров.Parameters("@CustomerId").Value = 1 … " Выполняем команду .ExecuteNonQuery() " Получаем OrderId добавленного заголовка заказа OrderId = .Parameters("@OrderId").Value " Очищаем параметры для следующей команды.Parameters.clear() End With " Вставляем позиции заказа " Настраиваем свойства Command With cmd .CommandType = CommandType.StoredProcedure .CommandText = "InsertOrderDetail" .Connection = conn " Добавляем параметры.Parameters.Add("@OrderId", SqlDbType.Int) .Parameters("@OrderId").SourceColumn = "OrderId" .Parameters("@OrderId").Direction = ParameterDirection.Input … " Устанавливаем значения параметров.Parameters("@OrderId").Value = OrderId .Parameters("@ItemId").Value = 100 … " Выполняем команду .ExecuteNonQuery() " Повторяем показанные выше строки для каждой позиции заказа End With " Фиксируем транзакцию tran.Commit() Catch " Откатываем транзакцию tran.Rollback() Finally " Код очистки. " Закрываем соединение. conn.Close() End Try

Как видите, две команды выполняются как часть одной транзакции. Если одна из них терпит неудачу, транзакция отменяется, и любые изменения в базе данных откатываются. Заключив код в блок try/catch/finally, вы гарантируете корректное выполнение транзакции: она фиксируется в самом конце блока try после успешного выполнения обеих SQL-команд. Любое исключение перехватывается в блоке catch, где транзакция отменяется и изменения, внесенные в ходе этой транзакции, откатываются.

Управление транзакциями через объекты ADO.NET приводит к менее эффективному блокированию, чем при использовании явных транзакций в хранимых процедурах. Причина в том, что при транзакциях ADO.NET, выполняемых вручную, требуется как минимум столько же двусторонних обменов данными с СУБД, сколько операций выполняется в транзакции плюс два обмена в ее начале и конце. Блокировки удерживаются в течение всего времени передачи вызовов из кода ADO.NET на сервер базы данных и обратно.

Транзакции MSMQ, выполняемые вручную

NET Framework предусматривает два вида поддержки транзакций MSMQ: внутреннюю (для транзакций вручную) и внешнюю (для автоматических транзакций). В первом случае в рамках транзакции возможен прием или передача нескольких сообщений. Во втором - сообщения участвуют в транзакциях DTC (Distributed Transaction Coordinator).

Транзакции MSMQ, выполняемые вручную, поддерживаются классом MessageQueueTransaction и обрабатываются исключительно ядром MSMQ. Подробности см. в статье Дункана Мак-Кензи (Duncan Mackenzie) (EN) .

Автоматические транзакции

Поддержка автоматических транзакций в.NET Framework опирается на службы MTS/COM+. COM+ использует DTC в качестве диспетчера и координатора транзакций при выполнении транзакций в распределенной среде. Это позволяет приложениям.NET выполнять транзакции, охватывающие разнообразные операции над множеством ресурсов, например вставку заказа в базу данных SQL Server, запись сообщения в очередь MSMQ (Microsoft Message Queue), отправку сообщения электронной почты и считывание информации из базы данных Oracle.

Предоставляя модель программирования на основе декларативных транзакций (declarative transactions), COM+ резко упрощает выполнение транзакций, в которых участвуют гетегрогенные ресурсы. Но учтите, что за это приходится расплачиваться снижением производительности, связанным с издержками взаимодействия DTC и COM; кроме того, поддержка вложенных транзакций отсутствует.

Страницы ASP.NET, методы Web-сервисов и. NET-классы можно помечать как транзакционные, присваивая им атрибут декларативной транзакции (declarative transaction attribute).

ASP.NET

<@ Page Transaction="Required" > Web-сервис ASP.NET <%@ WebService Language="VB" Class="Class1" %> <%@ assembly name="System.EnterpriseServices" %> … Public Class Class1 Inherits WebService <WebMethod(TransactionOption:= TransactionOption.RequiresNew) > _ Public Function Method1() …

Для участия в автоматических транзакциях. NET-класс должен наследовать от System.EnterpriseServices.ServicedComponent , который обеспечивает выполнение класса в COM+. Если вы сделаете именно так, COM+, взаимодействуя с DTC, создаст распределенную транзакцию и подключит к ней все необходимые ресурсы без вашего участия. Кроме того, вам нужно присвоить классу атрибут декларативной транзакции, чтобы определить его поведение при выполнении транзакции.

Visual Basic .NET

Visual C# .NET

public class Class1: ServicedComponent { … }

Транзакционный атрибут класса принимает одно из следующих значений:

  • Disabled. Указывает, что объект никогда не создается в транзакции COM+. Для поддержки транзакций объект может обращаться к DTC напрямую.
  • NotSupported. Указывает, что объект никогда не создается в транзакции.
  • Supported. Указывает, что объект выполняется в контексте транзакции своего создателя. Если объект сам является корневым или если его создатель не выполняется в транзакции, объект создается вне транзакции.
  • Required. Указывает, что объект выполняется в контексте транзакции своего создателя. Если объект сам является корневым или если его создатель не выполняется в транзакции, при создании такого объекта создается новая транзакция.
  • RequiresNew. Указывает, что объекту нужна транзакция и что при его создании создается новая транзакция.

В следующем коде содержится. NET-класс, настроенный на выполнение в COM+. Кроме того, атрибутам сборки присваиваются значения, необходимые для конфигурирования свойств COM+-приложения.

Visual Basic .NET

Imports System Imports System.Runtime.CompilerServices Imports System.EnterpriseServices Imports System.Reflection " Детали регистрации. " Имя COM+-приложения в том виде, в каком оно присутствует " в каталоге COM+ " Строгое имя (strong name) для сборки (assembly) Public Class Class1 Inherits ServicedComponent Public Sub Example1() … End Sub End Class

Visual C# .NET

using System; using System.Runtime.CompilerServices; using System.EnterpriseServices; using System.Reflection; // Детали регистрации. // Имя COM+-приложения в том виде, в каком оно присутствует // в каталоге COM+ // Строгое имя для сборки public class Class1: ServicedComponent { public void Example1() { … } }

<Assembly: ApplicationName(“Class1”) > указывает имя COM+-приложения, в которое устанавливаются компоненты сборки. <Assembly: ApplicationActivation(ActivationOption.Server) > определяет, является ли это приложение сервером или библиотекой. Когда вы указываете ApplicationActivation(ActivationOption.Server) , сборку необходимо установить в GAC (global assembly cache) с помощью утилиты командной строки gacutil (GacUtil.exe).

Для преобразования сборки в библиотеку типов, регистрации библиотеки типов и ее установки в заданное COM+-приложение можно использовать утилиту командной строки Regsvcs.exe. Кроме того, эта утилита настраивает свойства, добавленные в сборку программным способом. Например, если в сборке указано ApplicationActivation(ActivationOption.Server) , утилита создаст серверное приложение. Если вызванная сборка еще не установлена в COM+, исполняющая среда создаст и зарегистрирует библиотеку типов, а затем установит ее в COM+. COM+-приложение, созданное для сборки, можно просмотреть и настроить в оснастке Component Services.

Процесс создания, регистрации и использования обслуживаемых компонентов (serviced components) подробно рассматривается в разделе Writing Serviced Components (EN) руководства.NET Framework Developer’s Guide.

В следующем коде показывается транзакционный класс, сконфигурированный для запуска под управлением COM+, в котором в рамках транзакции выполняются две SQL-команды. Первая вставляет заголовок заказа в таблицу заказов и возвращает OrderId добавленного заказа. Этот OrderId используется второй командой при вставке позиций заказа в таблицу OrderDetails. Транзакция отменяется, если не удалось выполнить хотя бы одну из двух команд; при этом записи в базу данных не добавляются.

Visual Basic .NET

<Transaction(TransactionOption.Required) > Public Class Class1 Inherits ServicedComponent Public Sub Example1() … Try " Создаем новое соединение conn = New SqlConnection("ConnString") " Открываем соединение conn.Open() " Создаем новый объект Command cmd = New SqlCommand() " Вставляем заголовок заказа " Присваиваем значения свойствам Command With cmd1 .CommandType = CommandType.StoredProcedure .CommandText = "InsertOrderHeader" .Connection = conn " Добавляем входные и выходные параметры.Parameters.Add("@CustomerId", SqlDbType.Int) … .ExecuteNonQuery() " Очищаем параметры для следующей команды .Parameters.clear() End With " Вставляем позиции заказа " Настраиваем свойства Command With cmd .CommandType = CommandType.StoredProcedure .CommandText = "InsertOrderDetail" .Connection = conn " Добавляем параметры.Parameters.Add("@OrderId", SqlDbType.Int) … " Выполняем команду .ExecuteNonQuery() " Повторяем эти строки для каждой позиции заказа End With " Фиксируем транзакцию ContextUtil.SetComplete() Catch " Откатываем транзакцию ContextUtil.SetAbort() Finally " Код очистки End Try End Sub

Используя класс System.EnterpriseServices.ContextUtil , можно получить информацию о контексте COM+-объекта. Этот класс предоставляет методы SetComplete и SetAbort , позволяющие явным образом фиксировать и откатывать транзакцию. Легко догадаться, что метод ContextUtil.SetComplete вызывается в самом конце блока try, когда все операции выполнены успешно и нужно зафиксировать транзакцию. Все исключения перехватывается в блоке catch, где транзакция отменяется с помощью ContextUtil.SetAbort .

Кроме того, с помощью класса-атрибута (attribute class) System.EnterpriseServices.AutoComplete можно добиться, чтобы обслуживаемый компонент автоматически определял, фиксировать или откатывать транзакцию. Компонент «голосует» за фиксацию транзакции, если вызов метода завершился успешно. Если вызов метода привел к генерации исключения, транзакция автоматически отменяется; явный вызов ContextUtil.SetAbort не нужен. Чтобы воспользоваться этой возможностью, вставьте атрибут перед методом класса:

Visual Basic .NET

Public Class Class1 Inherits ServicedComponent Public Sub Example1() … End Sub End Class

Visual C# .NET

public class Class1: ServicedComponent { public void Example1() { … } }

Атрибут <AutoComplete > предлагает самый простой способ программирования транзакций, позволяя обходиться без явных фиксации и отката транзакций. Вы получите точно такую же функциональность, что и в предыдущем примере, где для отмены транзакции явно вызывался метод ContextUtil.SetAbort в блоке catch. Недостаток этого способа в том, что выполнение транзакции неочевидно и при сопровождении кода о ней можно забыть. Кроме того, нет возможности вывода дружественного к пользователю сообщения, если транзакция терпит неудачу. В таких случаях следует явно перехватывать все исключения, вызывать ContextUtil.SetAbort и показывать требуемое сообщение.

В системах, где нужно выполнять транзакции, использующие MSMQ и другие ресурсы, единственно возможный выбор - применение транзакций DTC или COM+. DTC координирует все диспетчеры ресурсов, участвующие в распределенной транзакции, а также управляет деятельностью, связанной с транзакциями. Пример распределенной транзакции MSMQ и SQL Server см. в статье Дункана Мак-Кензи Reliable Messaging with MSMQ and .NET (EN) .

Заключение

При использовании каждой из технологий работы с транзакциями приходится идти на компромис между производительностью приложения и удобством сопровождения кода. Запуск реализованной в хранимой процедуре транзакции базы данных обеспечивает наилучшую производительность, так как требуется лишь один двусторонний обмен информацией с базой данных. Кроме того, обеспечивается гибкость управления транзакциями, поскольку явно указываются начало и завершение транзакции. Но, хотя этот способ дает хорошую производительность и гибкость, приходится программировать на Transact SQL, что не так легко, как на языках.NET.

Транзакции, выполняемые вручную с помощью транзакционных объектов ADO.NET, просты в программировании. Благодаря явному применению инструкций начала и завершения транзакции, возможно гибкое управление границами транзакции. Платой за эту легкость и гибкость является снижение производительности за счет дополнительных двусторонних обменов информацией с базой данных при выполнении транзакции.

Автоматическая транзакция - единственно возможный выбор, когда транзакция использует несколько диспетчеров ресурсов, поддерживающих транзакции, например базы данных SQL Server, очереди сообщений MSMQ и т. д. Они значительно упрощают разработку приложений и предъявляют более низкие требования к квалификации программиста. Однако из-за того, что всю работу по координации выполняет служба COM+, возможны дополнительные издержки.



Рекомендуем почитать

Наверх