Лабораторная работа № 5

Тема: «Реализация распределенных транзакций»

Распределенные транзакции могут использовать гетерогенные ресурсы, поддерживающие транзакции, включать самые разнообразные операции, например выборку информации из базы данных 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 и др.

В Net 2.0 распределенные транзакции можно реализовать с помощью пространства имен System. Transactions. Пусть приложение работает с двумя базами данных. Код примера заносит две соответствующие друг другу операции вставки в одну транзакцию, и если одна из них не выполнится будет совершен откат обеих.

Объект TransactionScope всегда следует создавать в контексте команды Using, это гарантирует точное определение границ транзакции. После этого можно создать и установить столько соединений, сколько требуется, и каждая команда, выполняемая в контексте границ активной транзакции, будет автоматически ассоциирована с данной транзакцией.

НЕ нашли? Не то? Что вы ищете?

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

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

SQLConnection Conn = New SQLConnection("ConnString");

SQLCommand Cmd = New SQLCommand;

// Открываем соединение

Conn. Open();

// Начинаем транзакцию

SQLTransaction Txn = Conn. BeginTransaction();

// Настраиваем свойство Transaction на транзакцию, где выполняется

// SQL-команда

Cmd. Transaction = Txn;

Создайте, например, консольное приложение, в котором реализуйте, вставку данных в базу данных Universitet. mdf в разные таблицы с помощью разных подключений.

После компиляции и запуска приложений установите точку прерывания в конец выполнения команды первого подключения ExecuteNonQuery. Перейдите в SQL SERVER Management Studio и выполните команду SQL:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

BEGIN TRANSACTION

SELECT * FROM XXX

COMMIT

Вы должны получить сообщение, что вставлена только одна строка, вы увидите результат «грязного чтения». Уберите значение UNCOMMITTED. Запустите и проанализируйте результат.

Откройте Панель управления – Администрирование – службы компонентов – Координатор распределенных транзакций.

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

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

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

Чтобы SqlConnection смог занести себя в транзакцию, необходимо:

con2. EnlistTransaction(tran);

tran – экземпляр System. Transactions. Transaction, либо с помощью свойства System. Transactions. Transaction. Current получить доступ к текущей транзакции. После получения доступа к ней для поддержки многопоточности необходимо создать клон этой транзакции методом Transaction. Clone.

Transaction tran = Transaction. Current. Clone ();

Затем полученный клон необходимо передать в точку входа в поток.

Установите точку прерывания на строке

con2.EnlistTransaction(tran);

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

Теперь добавьте откат транзакции

//подключение занесено вручную

tran. Rollback();

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

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

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

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

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

Следующий пример демонстрирует включение режима автоматического управления транзакциями с уровнем изоляции RepeatableRead:

public void AutoCommitSessionTest()

{

OleDbConnectionStringBuilder builder =

ConnectionProvider. GetConnectionStringBuilder();

builder. Add("auto_commit", true);

builder. Add("auto_commit_level",

Convert. ToInt32(System. Data. IsolationLevel. RepeatableRead));

using(OleDbConnection con = new OleDbConnection(builder. ToString()))

{

con. Open();

OleDbCommand cmd =

new OleDbCommand("select count(*) from ХХХ", con);

Assert. IsTrue((int)cmd. ExecuteScalar() > 0);

}

}

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

auto_commit – включает режим автоматического управления транзакциями. Тип – boolean, по умолчанию – false.

auto_commit_level – задает уровень изоляции автоматических транзакций. Допустимые значения: Read Comitted, Repeatable Read, Snapshot. По умолчанию Repeatable Read. В уровни изоляции транзакций определены в перечислении IsolationLevel.

auto_commit_ddl – определяет режим выполнения DDL-запросов. Допустимые значения:

0 - Поддержка DDL-запросов отключена.

1 - Выполнять DDL-запрос в выделенной транзакции. Игнорируется, если установлено свойство auto_commit.

2 - Выполнять CommitRetaining после DDL-запроса. Игнорируется, если установлено свойство auto_commit.

Значение по умолчанию: 0.

Для организации распределенных транзакций служит объект TransactionsScope. Если он обнаружит, что в контексте используется несколько подключений, он будет использовать распределенную транзакцию, иначе – локальную. Следующий пример иллюстрирует поведение объекта TransactionsScope:

public void TransactionScopeTest()

{

// TransactionScrope автоматически свяжет локальные транзакции с

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

// на каждое подключение и одна распределенная.

using (TransactionScope scope = new TransactionScope())

{

// автоматически будет создана локальная транзакция

using(OleDbConnection con1 = ConnectionProvider. CreateConnection())

{

con1.Open();

// команда вставки

OleDbCommand cmdInsert = new OleDbCommand(

"insert into country (country, currency) values (:country, :currency)",

con1);

cmdInsert. Parameters. AddWithValue("country", "Russia");

cmdInsert. Parameters. AddWithValue("currency", "Rouble");

Assert. AreEqual(1, cmdInsert. ExecuteNonQuery());

// автоматически будет создана ещё одна локальная транзакция

OleDbConnection con2 = ConnectionProvider. CreateConnection();

con2.Open();

cmdInsert. Connection = con2;

cmdInsert. Parameters["country"].Value = "Latvia";

cmdInsert. Parameters["currency"].Value = "Lat";

Assert. AreEqual(1, cmdInsert. ExecuteNonQuery());

}

// Фиксация распределенной транзакции.

// Для всех локальных транзакций будет вызван метод Commit().

plete();

// Если ранее распределенная транзакция не была завершена, для всех локальных

// транзакций будет вызван метод Rollback при вызове IDispose. Dispose()

}

// TransactionScope будет использовать локальную транзакцию,

// т. к. все команды выполняются в одном контексте

using (TransactionScope scope = new TransactionScope())

{

using(OleDbConnection con1 = ConnectionProvider. CreateConnection())

{

con1.Open();

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

OleDbCommand cmdSelect = new OleDbCommand(

"select count(*) from country where country= :country", con1);

cmdSelect. Parameters. Add("country", OleDbType. BSTR);

// Удаляем записи

OleDbCommand cmdDelete = new OleDbCommand(

"delete from country where country= :country", con1);

cmdDelete. Parameters. Add("country", OleDbType. BSTR);

cmdSelect. Parameters["country"].Value = "Russia";

cmdDelete. Parameters["country"].Value = "Russia";

Assert. AreEqual(1, cmdSelect. ExecuteScalar());

Assert. AreEqual(1, cmdDelete. ExecuteNonQuery());

cmdSelect. Parameters["country"].Value = "Latvia";

cmdDelete. Parameters["country"].Value = "Latvia";

Assert. AreEqual(1, cmdSelect. ExecuteScalar());

Assert. AreEqual(1, cmdDelete. ExecuteNonQuery());

}

plete(); // фиксирование транзакции

} // откат транзакции, в случае возникновения ошибки

}

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

public void SQLTransactionTest()

{

using(OleDbConnection con1 = ConnectionProvider. CreateConnection())

{

con1.Open();

OleDbCommand cmd = new OleDbCommand(

"SET TRANSACTION READ ONLY WAIT ISOLATION LEVEL READ COMMITTED", con1);

cmd. ExecuteNonQuery();

mandText = "select count(*) from employee";

Assert. AreNotEqual(0, cmd. ExecuteScalar());

// подтверждение транзакции с последующим использованием её контекста

mandText = "COMMIT RETAIN";

cmd. ExecuteNonQuery();

mandText = "select count(*) from employee";

Assert. AreNotEqual(0, cmd. ExecuteScalar());

mandText = "COMMIT";

cmd. ExecuteNonQuery();

}

}

САМОСТОЯТЕЛЬНО:

Выполнить задание преподавателя.