Лабораторная работа № 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();
}
}
САМОСТОЯТЕЛЬНО:
Выполнить задание преподавателя.


