Транзакции и параллельность
Транзакция состоит из одной команды или группы команд, которые выполняются как пакет. Транзакции позволяют объединить несколько операций в одну единицу работы. Если в какой-либо точке транзакции возникает ошибка, может быть выполнен откат всех обновлений к их состоянию до начала транзакции.
Для обеспечения согласованности данных транзакция должна соответствовать свойствам ACID (атомарность, согласованность, изоляция и устойчивость). Большая часть систем реляционных баз данных, таких как Microsoft SQL Server, поддерживает транзакции, предоставляя блокировку, ведение журнала и средства управления транзакцией при выполнении клиентским приложением операций обновления, вставки или удаления.
Транзакции, которые задействуют множество ресурсов, могут снизить параллелизм, если слишком долго удерживают блокировки. Поэтому транзакции следует делать как можно короче.
В том случае, если в транзакции участвует несколько таблиц одной базы данных или одного сервера, явные транзакции в хранимых процедурах часто выполняются лучше. Транзакции можно создавать в хранимых процедурах SQL Server с использованием инструкций Transact-SQL BEGIN TRANSACTION , COMMIT TRANSACTION и ROLLBACK TRANSACTION .
Если для выполнения транзакций требуются различные диспетчеры ресурсов, например для транзакций между SQL Server и Oracle, необходимо использовать распределенную транзакцию.
В этом разделе
Локальные транзакции
Демонстрирует выполнение транзакций на базе данных.
Распределенные транзакции
Описывает выполнение распределенных транзакций в ADO.NET.
Интеграция System.Transactions с SQL Server
Описывает интеграцию System.Transactions с SQL Server для работы с распределенными транзакциями.
Оптимистичный параллелизм
Описывается оптимистичный и пессимистичный параллелизм и проверка на выявление нарушений параллелизма.
См. также раздел
- Основы транзакций
- Подключение к источнику данных
- Команды и параметры
- Объекты DataAdapter и DataReader
- DbProviderFactories
- Общие сведения об ADO.NET
Что такое транзакция в программировании
Рассмотрим второй вариант, изображенный на рис. 1.10. Данные в поле «Номер» были изменены на другие. Таким образом, в полях, имеющих прежнее значение «1», данные стали иметь значение «6». В этой ситуации тоже теряются нужные данные.
В обоих примерах имело место нарушение целостности БД, то есть информация, хранящаяся в БД, стала недостоверной из-за искажения связей.
Нарушение ссылочной целостности может возникнуть в нескольких случаях:
— удаление записи из главной таблицы, без удаления связанных записей в дочерней таблице;
— изменение значения поля связи главной таблицы, без изменения ключа дочерней;
— изменение ключа дочерней таблицы без изменения поля связи главной. Для предотвращения потери ссылочной целостности, используется механизм каскадных изменений. Суть его довольно проста:
— При изменении значения поля связи в главной таблице должен быть синхронно изменен ключ, то есть его значение.
— При удалении записи в родительской таблице обязательно следует удалить все связанные записи.
Ограничения на изменение полей связи и их каскадное удаление могут быть наложены на таблицы при их создании. Эти ограничения обычно хранятся Понятие транзакции 23 в системных таблицах наряду с индексами, триггерами и хранимыми процедурами. В некоторых случаях забота о сохранении ссылочной целостности ложиться на плечи разработчика.
Понятие транзакции
Транзакция — это последовательность действий с базой данных, в которой либо все действия выполняются успешно, либо не выполняется ни одно из них. Для того чтобы наглядно продемонстрировать суть транзакции, стоит рассмотреть простой пример. На склад пришла новая партия какого-либо товара. Необходимо принять его и занести информацию о нем в базу данных. Возникает некая цепочка действий:
— Увеличить количество единиц товара на складе.
— Ввести дату поступления новой партии.
— Ввести номер площадки, где будет храниться новая партия товара. Предположим, что на последнем шаге произошла какая-то ошибка. Товар был зарегистрирован, его количество было увеличено, но место его расположения на складе потеряно. Такая ситуация недопустима. Транзакция должна выполняться полностью. Только тогда изменения сохранятся в базе данных. В противном случае, если один из операторов транзакции по какой-либо причине не был выполнен, измененные данные в базе данных не сохраняются, а транзакция отменяется.
Физически транзакция представляет собой последовательность команд, производящих с базой данных те или иные действия. Главная особенность транзакций заключается в том, что все действия должны выполниться, иначе будет отменена вся транзакция.
Транзакция может быть неявной и явной. Неявная транзакция стартует автоматически, а по завершении также автоматически подтверждается или отменяется. Явной транзакцией управляет программист, используя для этого средства языка SQL.
Библиотека программиста. 2009.
Администратор: admin@programmer-lib.ru
Транзакции баз данных
— это набор операций в базе данных, которые должны быть либо все выполнены, либо все не выполнены. Транзакции применяются для обеспечения безопасности, верности и непротиворечивости данных в таблице.
Транзакции очень важны тогда, когда при работе с базой данных требуется взаимодействие с несколькими таблицами или несколькими хранимыми процедурами (или с сочетанием неделимых объектов базы данных). Классический пример транзакции — процесс перевода денежных средств с одного банковского счета на другой. Например, если вам понадобилось перевести $500 с депозитного счета на текущий счет, то нужно выполнить в режиме транзакции следующие шаги:
- банк должен снять $500 с вашего депозитного счета;
- затем банк должен добавить $500 на ваш текущий счет.
Вряд ли вам бы понравилось, если бы деньги были сняты с депозитного счета, но не переведены (из-за какой-то банковской ошибки) на текущий счет. Но если эти шаги упаковать в транзакцию базы данных, то СУБД гарантирует, что все взаимосвязанные шаги будут выполнены как единое целое. Если любая часть транзакции выполнится неудачно, то будет произведен откат (rollback) всей транзакции в исходное состояние. А если все шаги будут выполнены успешно, то транзакция будет зафиксирована (committed).
Если вы уже читали о транзакциях, то, возможно, вам встречалось сокращение ACID. Оно означает четыре ключевых свойства классической транзакции: атомарность (Atomic — все или ничего), целостность (Consistent — на протяжении транзакции данные остаются в непротиворечивом состоянии), изолированность (Isolated — транзакции не мешают одна другой) и устойчивость (Durable — транзакции сохраняются и протоколируются).
Оказывается, в платформе .NET есть несколько способов поддержки транзакций. Здесь мы рассмотрим объект транзакции для поставщика данных ADO.NET (SqlTransaction в случае System.Data.SqlClient). Библиотеки базовых классов ADO.NET также обеспечивают поддержку транзакций в многочисленных API-интерфейсах, которые перечислены ниже:
Это пространство имен (из сборки System.EnterpriseServices.dll) содержит типы, позволяющие выполнить интеграцию с уровнем времени выполнения СОМ+, в том числе и поддержку распределенных транзакций.
Это пространство имен (из сборки System.Transactions.dll) содержит классы, позволяющие писать собственные транзакционные приложения и диспетчеры ресурсов для различных служб (MSMQ, ADO.NET, СОМ+ и т.д.).
Windows Communication Foundation
WCF API предоставляет службы для работы с транзакциями с различными классами распределенного связывания.
Windows Workflow Foundations
WF API предоставляет транзакционную поддержку для рабочих потоков.
Кроме встроенной поддержки транзакций в библиотеках базовых классов .NET, можно пользоваться и возможностями языка SQL используемой СУБД. Например, можно написать хранимую процедуру, в которой задействованы операторы BEGIN TRANSACTION, ROLLBACK и COMMIT.
Основные члены объекта транзакции ADO.NET
Типы для работы с транзакциями существуют во всех библиотеках базовых классов, но мы будем рассматривать объекты транзакции, которые имеются в поставщиках данных ADO.NET — все они порождены от DBTransaction и реализуют интерфейс IDbTransaction. Вспомните, что IDbTransaction определяет ряд членов:
public interface IDbTransaction : IDisposable < IDbConnection Connection < get; >IsolationLevel IsolationLevel < get; >void Commit(); void Rollback(); >
Обратите внимание на свойство Connection, которое возвращает ссылку на объект подключения, инициировавший данную транзакцию (как мы увидим, объект транзакции можно получить от данного объекта подключения). Метод Commit() вызывается, если все операции в базе данных завершились успешно. При этом все ожидающие изменения фиксируются в хранилище данных. А метод Rollback() можно вызвать при возникновении исключения времени выполнения, чтобы сообщить СУБД, что все ожидающие изменения следует отменить и оставить первоначальные данные без изменений.
Свойство IsolationLevel объекта транзакции позволяет указать степень защиты транзакции от действий параллельных транзакций. По умолчанию транзакции полностью изолируются до их фиксации. Полную информацию о значениях перечисления IsolationLevel можно найти в документации по .NET Framework 4.0 SDK.
Кроме членов, определенных интерфейсом IDbTransaction, в типе SqlTransaction определен дополнительный член Save(), который предназначен для определения точек сохранения (save point). Эта концепция позволяет откатить неудачную транзакцию до указанной точки, не выполняя откат всей транзакции. При вызове метода Save() с помощью объекта SqlTransaction можно задать произвольный строковый псевдоним.
А при вызове Rollback() можно указать этот псевдоним в качестве аргумента, чтобы выполнить частичный откат (partial rollback). При вызове Rollback() без аргументов будут отменены все ожидающие изменения.
Добавление таблицы CreditRisks в базу данных AutoLot
А теперь рассмотрим, как можно использовать транзакции в ADO.NET. Вначале откройте окно Server Explorer из Visual Studio 2010 и добавьте в базу данных AutoLot новую таблицу с именем CreditRisks, которая содержит точно такие же столбцы, что и таблица Customers, созданная ранее: CustID (первичный ключ), FirstName и LastName. Эта таблица предназначена для отсеивания нежелательных клиентов с плохой кредитной историей. После добавления новой таблицы в диаграмму базы данных AutoLot ее реализация будет такой:
Как и предыдущий пример с пересылкой денег с депозита на текущий счет, этот пример, где подозрительный клиент перемещается из таблицы Customers в таблицу CreditRisks, должен работать под недремлющим оком транзакционной области (ведь вы хотите запомнить идентификаторы и имена некредитоспособных клиентов). А именно, необходимо гарантировать, что либо будет выполнено успешное удаление текущих кредитных рисков из таблицы Customers с последующим их добавлением в таблицу CreditRisks, либо ни одна из этих операций не будет выполнена.
В производственной среде вам не понадобится создавать совершенно новую таблицу базы данных для подозрительных клиентов. Достаточно добавить в таблицу Customers логический столбец IsCreditRisk. Эта новая таблица предназначена просто для опробования работы с простыми транзакциями.
Добавление метода транзакции в InventoryDAL
А теперь посмотрим, как работать с транзакциями ADO.NET программным образом. Откройте созданный ранее проект AutoLotDAL Code Library и добавьте в класс InventoryDAL новый общедоступный метод ProcessCreditRisk(), предназначенный для работы с кредитными рисками (в данном примере для простоты не используется параметризованный запрос, но в производственном методе его следует задействовать):
public void ProcessCreditRisk(bool throwEx, int custId) < // Выборка имени по идентификатору клиента string fName = string.Empty; string lName = string.Empty; SqlCommand cmdSelect = new SqlCommand(string.Format("Select * from Customers where CustID = ", custId), connect); using (SqlDataReader dr = cmdSelect.ExecuteReader()) < if (dr.HasRows) < dr.Read(); fName = (string)dr["FirstName"]; lName = (string)dr["LastName"]; >else return; > // Создание объектов команд для каждого шага операции. SqlCommand cmdRemove = new SqlCommand( string.Format("Delete from Customers where CustID = ", custId), connect); SqlCommand cmdInsert = new SqlCommand(string.Format("Insert Into CreditRisks" + "(CustID, FirstName, LastName) Values" + "(, '', '')", custId, fName, lName), connect); // Получаем из объекта подключения. SqlTransaction tx = null; try < tx = connect.BeginTransaction(); // Включение команд в транзакцию cmdInsert.Transaction = tx; cmdRemove.Transaction = tx; // Выполнение команд. cmdInsert.ExecuteNonQuery(); cmdRemove.ExecuteNonQuery(); // Имитация ошибки. if (throwEx) < throw new ApplicationException("Ошибка базы данных! Транзакция завершена неудачно."); >// Фиксация. tx.Commit(); > catch (Exception ex) < Console.WriteLine(ex.Message); // При возникновении любой ошибки выполняется откат транзакции. tx.Rollback(); >>
Здесь используется входной параметр типа bool, который указывает, нужно ли генерировать произвольное исключение при попытке обработки нежелательного клиента. Это позволит имитировать непредвиденные ситуации, которые могут привести к неудачному завершению транзакции. Понятно, что здесь это сделано лишь в демонстрационных целях; в реальности метод транзакции не должен позволять вызывающему процессу разрушать всю логику по своему усмотрению!
Мы используем два объекта SqlCommand, представляющие каждый шаг предстоящей транзакции. После получения имени и фамилии клиента по входному параметру custID с помощью метода BeginTransaction() объекта подключения получаем нужный объект SqlTransaction. Если этого не сделать, логика вставки/удаления не будет выполняться в транзакционном контексте.
Если (и только если) значение логического параметра равно true, то после вызова ExecuteNonQuery() для обеих команд генерируется исключение. В этом случае все ожидающие подтверждения операции базы данных аннулируются. Если исключение не было сгенерировано, оба шага фиксируются в таблицах базы данных при вызове Commit(). Скомпилируйте измененный проект AutoLotDAL и проверьте, нет ли ошибок.
Транзакции в распределённых системах
Пусть у нас есть несколько узлов (процессов), которых хранят какие-то непересекающиеся данные. Например, на одном узле хранятся банковские счета пользователей на «А», на другом — на «Б», и так далее.
Тогда мы можем хотеть транзакционно изменять данные на разных узлах (см. BEGIN TRANSACTION и COMMIT TRANSACTION в SQL).
Определение: |
Транзакция — это единица работы над множеством элементов из базы данных, которую можно в процессе работы целиком отменить (либо сама база данных, либо пользователь, см. ROLLBACK TRANSACTION ), либо подтвердить (база данных может иногда не справиться, тогда транзакция отменяется). |
У транзакции обычно выделяют свойства по аббревиатуре ACID:
- Atomicity (атомарность) — транзакция либо полностью применила все свои изменения, либо полностью откатилась (отменилась)
- Consitency (согласованность) — в конце транзакции система находится в согласованном состоянии
- Isolation (изолированность) — параллельные транзакции не должны влиять друг на друга (например, при помощи phantom reads и non-repeatable reads), а должны выполняться как будто последовательно
- Durability (надёжность) — завершённые (commited) транзакции сохраняются даже в случае сбоев и перезапуска системы
Атомарность и надёжность
Предположим, что нам нужны только атомарность и надёжность. Самое сложное — откатывать транзакцию, если что-то пошло не так. Есть два основных способа этого добиться.
Undo log
Каждый узел сначала записывает предыдущие значения в надёжный журнал (лог, обычно append-only и сохраняется на диск), а только потом изменяет состояние в памяти. Когда транзакция подтверждается, надо записать произведённые изменения в надёжное место и можно стереть кусок журнала. Если транзакцию надо откатить (например, после перезапуска системы в журнале нет записи «транзакция успешна»), то мы идём с конца журнала и восстанавливаем старые значения.
Redo log
Мы вообще не делаем изменения в данных до подтверждения транзакции, а просто пишем в журнал все операции, которые надо произвести с данными. Когда транзакция успешно завершается, у нас две опции:
- Изменить данные прямо на диске.
- Записать об этом в журнал на диск (append-only), а состояние просто каждый раз восстанавливать из этого журнала. Иногда делать checkpoint’ы для сохранения состояния. Это сейчас на практике популярнее.
Согласованность и изоляция
В базах данных различают разные уровни изоляции (isolation level), максимальный уровень — сериализуемость (serializability). Это когда все транзакции можно упорядочить и получить согласованную историю, как если бы они выполнялись последовательно.
Можно брать блокировку сразу на все узлы, но это не очень эффективно и распределённая блокировка — это сложно, поэтому обычно дают блокировки разным данным в пределах одного узла. Чтобы сделать транзакцию, надо взять нужные блокировки (даже на чтение) и отпустить их, но при этом не вляпаться во взаимные блокировки или несогласованность (по-факту то транзакция не мгновенная). Для этого используется алгоритм двухфазной блокировки.
Более сложная штука — MVCC (MultiVersion Concurrency Control), это когда мы для каждой транзакции создаём «снимок» базы данных (наверняка при помощи персистентных структур данных) и дальше транзакция работает с ним. Например, в транзакциях только на чтение это позволяет экономить блокировки. А если появились записи, то надо как-то пробовать решать конфликты или даже просто откатывать транзакцию, если данные, которые она читала, уже кем-то были изменены.
Подтверждение транзакции в распределённой системе
Блокировки у нас локальны и берутся просто, но нам надо, чтобы все участники атомарно пришли к решению завершать транзакцию. Можно использовать алгоритмы распределённого консенсуса, но они сложные. Классическое решение в базах данных — алгоритм двухфазного коммита (не имеет никакого отношения к двухфазной блокировке).