Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции_CSharp_4.docx
Скачиваний:
37
Добавлен:
02.11.2018
Размер:
237.61 Кб
Скачать

4.15. Работа с транзакциями

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

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

• Согласованность (Consistent). Транзакция переводит базу данных из одного стабильного состоянии в другое.

• Изолированность (Isolated). Транзакция не должна влиять на другую транзакцию, выполняющуюся в то же время.

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

Транзакции часто используются во многих бизнес-приложениях, потому что они обеспечивают устойчивость и предсказуемость системы. Чтобы в таких программных системах можно было применить концепцию транзакций, их должен поддерживать источник данных. Современные СУБД, такие как MS SQL Server 2005 и Oracle 9i, обеспечивают мощную поддержку транзакций. Например, SQL Server 2005 обеспечивает поддержку таких операторов T-SQL, как BEGIN TRANSACTION (начать транзакцию), SAVE TRANSACTION (сохранить транзакцию), COMMIT TRANSACTION (зафиксировать транзакцию) и ROLLBACK TRANSACTION (откатить транзакцию).

Транзакции можно разделить на две категории ‑ локальные и распределен­ные. Локальная транзакция использует поддерживающий транзакции источник данных (например, SQL Sever) и не выходит за рамки одного соединения. Когда все участвующие в транзакции данные содержатся в одной базе данных, сама база обеспечивает выполнение правил ACID. Распределенная транзакция охватывает несколько источников данных, и ей может потребоваться читать сообщения с сервера очереди сообщений (Message Queue Server), выбирать данные из базы данных SQL Server и записывать их в другие базы данных.

ADO.NET поддерживает транзакции одиночной базы, которые отслеживаются на основе подключений. Провайдеры данных имеют собственные реализации для класса транзакций (например, класс SqlTransaction). Все такие классы реализуют интерфейс IDbTransaction. Метод Commit() идентифицирует транзакцию как успешную. Если этот метод выполнился без ошибки, то все ожидающие записи изменения записываются в базу данных. Метод Rollback() помечает транзакцию как неудачную, и все ожидающие изменения аннулируются. База данных остается в прежнем состоянии. Обычно эти методы используются совместно. В следующем фрагменте кода демонстрируется их наиболее характерное использование. Для связи команды и транзакции используется свойство команды Transaction.

using (var conn = new SqlConnection(". . ."))

{

conn.Open();

// получить транзакцию можно, если подключение открыто!

var trans = conn.BeginTransaction();

command1.Transaction = trans;

command2.Transaction = trans;

try

{

command1.ExecuteNonQuery();

command2.ExecuteNonQuery();

trans.Commit();

}

catch (Exception)

{

trans.Rollback();

throw;

}

finally

{

conn.Close();

}

}

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

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

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

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

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

Для решения этих проблем разработаны различные уровня изоляции транзакции. Рассмотрим различные значения уровней изоляции, которые доступны в ADO.NET. Эти значения входят в перечисление IsolationLevel:

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

• ReadCommited. Транзакция не может считывать данные, с которыми работают другие транзакции. Применение этого уровня изоляции исключает проблему «грязного» чтения. Однако данные могут быть изменены до завершения транзакции. Это стандартный уровень изоляции как для MS SQL Server, так и для Oracle.

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

• Snapshot. Этот уровень изоляции уменьшает вероятность установки блокировки строк, сохраняя копию данных, которые одно приложение может чи­тать, в то время как другое модифицирует эти же данные. Другими словами, если транзакция А модифицирует данные, то транзакция В не увидит вы­полненные изменения. Но важно то, что транзакция В не будет заблокирована и будет читать снимок данных, сделанный перед началом транзакции А. В SQL Server 2005 изоляция Snapshot перед использованием должна быть разрешена на уровне самой базы данных. Это можно сделать с помощью следующей SQL-команды: ALTER DATABASE ИмяБД SET ALLOW SNAPSHOT ISOLATION ON

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

Эти значения уровня изоляций могут быть заданы в процессе инициализации транзакции методом BeginTransaction() класса DbConnection. Узнать значение уровня изоляции можно с помощью свойства IsolationLevel объекта транзакции.

Коротко остановимся на возможностях работы с распределенными транзакциями. В платформе .NET версии 2.0 распределенные транзакции можно легко реализовать с помощью пространства имен System.Transactions. Рассмотрим простой пример. Приложение работает с двумя базами данных. Первая база данных, Credits, содержит одну таблицу Credits с двумя столбцами CreditID и CreditAmount. Аналогично, есть вторая таблица Debits с таблицей Debits, со­стоящей из двух столбцов DebitID и DebitAmount. Код примера заносит две соответствующие друг другу операции вставки в одну транзакцию. То есть, если операция добавления денег пошла неудачно, то необходимо выполнить откат и для ранее вставленной строки снятия суммы.

using (var myTrans = new TransactionScope())

{

using (var conn_1 = new SqlConnection("..."))

{

conn_1.Open();

var myCmd = conn_1.CreateCommand();

myCmd.CommandText =

"Insert into Credits (CreditAmount) Values (100)";

myCmd.ExecuteNonQuery();

}

Console.WriteLine("Первая операция завершена");

Console.ReadLine();

using (var conn_2 = new SqlConnection("..."))

{

conn_2.Open();

var myCmd = conn_2.CreateCommand();

myCmd.CommandText =

"Insert into Debits (DefeitAmount) Values (100)";

myCmd.ExecuteNonQuery();

}

myTrans.Complete();

}

Как видите, транзакция оформляется с помощью блока using. Приложение создает новый экземпляр TransactionScope, который определяет включаемую в транзакцию часть кода. И весь код между конструктором TransactionScope и вызовом Dispose() для этого экземпляра TransactionScope заносится в создаваемую транзакцию. Затем, с помощью блока using вступают в игру два объекта SqlConnection, подключающиеся к двум различным базами данных.

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

1 Более точно, вначале производится поиск соответствия с учетом регистра, если соответствие не найдено – повторный поиск без учета регистра.

1 С помощью метода ридера GetName() можно узнать имя конкретного столбца, указав его номер. Метод GetDataTypeName() принимает целое число, соответствующее порядковому номеру столбца, и возвращает в строковом представлении тип данных этого столбца.

1 Это имеет смысл в том случае, если выполняется команда, которая комбинирует несколько SQL-операторов. Например, команда с таким текстом:SELECT * FROM Songs; DELETE FROM Songs WHERE id = 1

1 Для поставщика MS SQL Server таблица с описанием содержит следующие столбцы: ColumnName, ColumnOrdinal, ColumnSize, NumericPrecision, NumericScale, IsUnique, IsKey, BaseServerName, BaseCatalogName, BaseColumnName, BaseSchemaName, BaseTableName, DataType, AllowDBNull, ProviderType, IsAliased, IsExpression, IsIdentity, IsAutoIncrement, IsRowVersion, IsHidden, IsLong, IsReadOnly. За более подробной информацией обратитесть к справке по .NET Framework.

1 При занесении информации в таблицу при помощи адаптера автоматически настраиваются следующие свойства: AllowDBNull, Caption, ColumnName, DataType, Ordinal, Table, Unique.

2 Класс System.DBNull соответствует понятию NULL в реляционных БД и имеет единственное статическое поле только для чтения Value.

2 Для столбцов с автоприращением можно принудительно указать произвольное целочисленное значение при занесении информации в строку таблицы.

1 Подробнее о других способах получения и поиска строк будет рассказано далее.

1 Это значение получено из базы.

1 Как и в случае с использованием свойства Expression класса DataColumn, для подробного ознакомления с синтаксисом построения выражений отсылаем заинтересованных читателей к справке MSDN.

1 В данном параграфе нас будет интересовать только команда для выборки данных.

1 Существует перегруженная версия метода Fill() для заполнения одной таблицы (вне набора):DataTable dt = new DataTable();

da.Fill(dt);