Главная страница » Что такое транзакция в информатике

Что такое транзакция в информатике

  • автор:

Транзакция (информатика)

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

Различают последовательные (обычные), параллельные и распределённые транзакции. Распределённые транзакции подразумевают использование больше чем одной транзакционной системы и требуют намного более сложной логики (например, two-phase commit — двухфазный протокол фиксации транзакции). Также, в некоторых системах реализованы автономные транзакции, или под-транзакции, которые являются автономной частью родительской транзакции.

Содержание

Пример транзакции

Пример: необходимо перевести с банковского счёта номер 5 на счёт номер 7 сумму в 10 денежных единиц. Этого можно достичь, к примеру, приведённой последовательностью действий:

  • Начать транзакцию
  • Окончить транзакцию

Эти действия представляют собой логическую единицу работы «перевод суммы между счетами», и таким образом, являются транзакцией. Если прервать данную транзакцию, к примеру, в середине, и не аннулировать все изменения, легко оставить владельца счёта номер 5 без 10 единиц, тогда как владелец счета номер 7 их не получит.

Свойства транзакций

Одним из наиболее распространённых наборов требований к транзакциям и транзакционным системам является набор ACID (Atomicity, Consistency, Isolation, Durability). Вместе с тем, существуют специализированные системы с ослабленными транзакционными свойствами [1] .

Уровни изоляции транзакций

В идеале транзакции разных пользователей должны выполняться так, чтобы создавалась иллюзия, что пользователь текущей транзакции — единственный. Однако в реальности, по соображениям производительности и для выполнения некоторых специальных задач, СУБД предоставляют различные уровни изоляции транзакций. Уровни описаны в порядке увеличения изоляции транзакций и надёжности работы с данными

  • 0 — Неподтверждённое чтение (Read Uncommitted, Dirty Read, грязное чтение) — чтение незафиксированных изменений своей транзакции и параллельных транзакций, возможны нечистые, неповторяемые чтения и фантомы
  • 1 — Подтверждённое чтение (Read Committed) — чтение всех изменений своей транзакции и зафиксированных изменений параллельных транзакций, нечистые чтения невозможны, возможны неповторяемые чтения и фантомы;
  • 2 — Повторяемое чтение (Repeatable Read, Snapshot) — чтение всех изменений своей транзакции, любые изменения, внесённые параллельными транзакциями после начала своей, недоступны, нечистые и неповторяемые чтения невозможны, возможны фантомы;
  • 3 — Упорядоченный — (Serializable, сериализуемый) — упорядоченные (сериализуемые) транзакции. Идентичен ситуации, при которой транзакции выполняются строго последовательно, одна после другой, то есть результат действия которых не зависит от порядка выполнения шагов транзакции (запрещено чтение всех данных, изменённых с начала транзакции, в том числе и своей транзакцией). Фантомы невозможны.

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

В СУБД уровень изоляции транзакций можно выбрать как для всех транзакций сразу, так и для одной конкретной транзакции. По умолчанию в большинстве баз данных используется уровень 1 (Read Committed). Уровень 0 используется в основном для отслеживания изменений длительных транзакций или для чтения редко изменяемых данных. Уровни 2 и 3 используются при повышенных требованиях к изолированности транзакций.

Реализация

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

Первые коммерческие СУБД (к примеру, IBM DB2), пользовались исключительно блокировкой доступа к данным для обеспечения свойств ACID. Но большое количество блокировок приводит к существенному уменьшению производительности. Есть два популярных семейства решений этой проблемы, которые снижают количество блокировок:

    (write ahead logging, WAL) (shadow paging) [2] .

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

При упреждающей журнализации, используемой в Sybase и MS SQL Server до версии 2005, все изменения записываются в журнал, и только после успешного завершения — в базу данных. Это позволяет СУБД вернуться в рабочее состояние после неожиданного падения системы. Теневые страницы содержат копии тех страниц базы данных на начало транзакции, в которых происходят изменения. Эти копии активизируются после успешного завершения. Хотя теневые страницы легче реализуются, упреждающая журнализация более эффективна [3]

Дальнейшее развитие технологий управления базами данных привело к появлению безблокировочных технологий. Идея контроля за параллельным доступом с помощью временных меток (timestamp-based concurrency control) была развита и привела к появлению многоверсионной архитектуры MVCC. Эти технологии не нуждаются ни в журнализации изменений, ни в теневых страницах. Архитектура, реализованная в Oracle 7.х и выше, записывает старые версии страниц в специальный сегмент отката, но они все ещё доступны для чтения. Если транзакция при чтении попадает на страницу, временная метка которой новее начала чтения, данные берутся из сегмента отката (то есть используется «старая» версия). Для поддержки такой работы ведётся журнал транзакций, но в отличие от «упреждающей журнализации», он не содержит данных. Работа с ним состоит из трёх логических шагов:

  1. Записать намерение произвести некоторые операции
  2. Выполнить задание, копируя оригиналы изменяемых страниц в сегмент отката
  3. Записать, что всё сделано без ошибок

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

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

Firebird вообще не имеет ни журнала изменений, ни сегмента отката, а реализует MVCC, записывая новые версии строк таблиц прямо в активное пространство данных. Так же поступает MS SQL 2005. Теоретически это даёт максимальную эффективность при параллельной работе с данными, но ценой является необходимость «сборки мусора», то есть удаления старых и уже не нужных версий данных.

Теория транзакций с примерами из Microsoft SQL Server

Думаю, многие из вас работали с транзакциями и представляют, как применить к базе данных консистентную последовательность операций. Сегодня мы узнаем, что происходит с транзакцией, когда мы отправляем ее в СУБД. Мы познакомимся с классической теорией транзакций и тем, какие существуют подходы для формирования корректных расписаний. Кроме того, постараемся связать эту теорию с практикой на примере известной СУБД Microsoft SQL Server. (Сегодня будет много информации, приготовьтесь!)

Сериализуемые расписания

Транзакции

Начнем с определения того, что такое транзакция:

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

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

В MS SQL Server существует 2 типа транзакций:

  1. Неявные — отдельные операции INSERT, UPDATE или DELETE.
  2. Явные — набор операций языка T-SQL, начинающийся с инструкции BEGIN TRANSACTION и заканчивающийся COMMIT или ROLLBACK.

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

Аномалии транзакций

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

1) Потерянное обновление. При обновлении поля двумя транзакциями одно из изменений теряется.

Транзакция 1 Транзакция 2
SELECT x FROM tbl WHERE y=1; SELECT x FROM tbl WHERE y=1;
UPDATE tbl SET x=5 WHERE y=1;
UPDATE tbl SET x=3 WHERE y=1;

2) Грязное чтение. Чтение данных, полученных в результате действия транзакции, которая после этого откатится.

Транзакция 1 Транзакция 2
SELECT x FROM tbl WHERE y=1;
UPDATE tbl SET x=x+1 WHERE y=1;
SELECT x FROM tbl WHERE y=1;
ROLLBACK;

3) Неповторяющееся чтение. Возникает, когда в течение одной транзакции при повторном чтении данные оказываются перезаписанными.

Транзакция 1 Транзакция 2
SELECT x FROM tbl WHERE y=1; SELECT x FROM tbl WHERE y=1;
UPDATE tbl SET x=x+1 WHERE y=1;
COMMIT;
SELECT x FROM tbl WHERE y=1;

4) Фантомное чтение. Отличие от предыдущей аномалии в том, что при повторном чтении одна и та же выборка дает разные множества строк.

Транзакция 1 Транзакция 2
SELECT SUM(x) FROM tbl;
INSERT INTO tbl (x, y) VALUES (5, 3);
SELECT SUM(x) FROM tbl;

Вычислительная модель

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

  1. Элементарные операции, определенные над объектами данных.
  2. Транзакции: последовательности или частично упорядоченные множества элементарных операций.
  3. Расписания (или истории), описывающие конкурентное выполнение транзакций.
  4. Критерии корректности расписаний (историй).
  5. Алгоритмы управления транзакциями, обеспечивающие получение корректных расписаний.

Сегодня мы будем рассматривать транзакции в контексте страничной модели. В этой модели база данных представляется как набор независимых страниц ` (x, y, z) `, над которыми возможны две атомарные операции: read и write с полным или частичным порядком внутри транзакции.

Критерии корректности

Первоначально определим понятие истории. История — упорядоченная совокупность операций нескольких транзакций, включая операции завершения транзакции (commit, abort). Расписанием называется префикс истории. Пример (индексы соответствуют транзакциям): ` r_1(x) r_2(x) w_1(x) w_2(x) c_1 c_2 `

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

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

Семантика Эрбрана

С помощью этого понятия определяются последующие сериализуемости. Семантика Эрбрана основывается на двух предположениях:

  1. Каждая операция чтения возвращает последнее значение страницы, записанное предшествующей операции записи.
  2. Операция записи записывает значение, потенциально зависящее от всех значений, прочитанных предшествующими операциями чтения той же транзакции.

Выпишем семантику Эрбрана для страницы x. Для этого, начиная с последней операции записи, раскрываем операции в соответствии с правилами выше. Нулевая транзакция выполняется первоначально и инициализирует значения базы данных.

Расписание Семантика Эрбрана
r1(x) r2(y) w1(x) w3(z) r2(x) w2(z) w2(x) f2x(f1x(f0x)), f2y(f0y))

Сериализуемость по конечному состоянию

Расписания эквивалентны по конечному состоянию, если:

  • равны множества операций
  • семантики Эрбрана равны для всех элементов данных

Расписание называется сериализуемым по конечному расписанию (FSR), если оно эквивалентно серийному по конечному состоянию. Пример неэквивалентных расписаний:

Расписание Семантика Эрбрана
r1(x) r2(y) w1(y) w2(y) c1 c2 f2y(f0y())
r1(x) w1(y) r2(y) w2(y) c1 c2 f2y(f1y(f0y()))

Этот тип сериализуемости решает проблему потери обновления, но с другими аномалиями справиться он не в силах.

Сериализуемость по видимому состоянию

В дополнение к условиям предыдущей эквивалентности для эквивалентности по видимому состоянию требуется, чтобы все операции чтения в расписаниях следовали после одинаковых операций записи. Пример расписания, сериализуемого по видимому (VSR), но не по конечному состоянию: ` w_1(x) r_2(x) r_2(y) w_1(y) c_1 c_2 `

VSR по-прежнему не может справиться с аномалиями грязного чтения, так как не учитывает оборванные транзакции. Кроме того, проверка расписания на эту сериализуемость является NP-полной.

Сериализуемость по конфликтам

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

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

  1. Перестановка соседних операций чтения
  2. Перестановка соседних операций над разными элементами

Диспетчеры и протоколы

Функционирование диспетчера

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

В MS SQL Server, так же как и во многих других СУБД есть два вида конкурентного доступа (протоколов):

  1. Пессимистичные. Для предотвращения нарушения сериализуемости двумя транзакциями применяются блокировки.
  2. Оптимистичные. Протокол основывается на предположении маловероятности одновременного изменения данных двумя транзакциями. В случае нарушения сериализуемости транзакция обрывается.

Протокол Two Phase Locking

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

В SQL сервере используется модификация 2PL, именуемая SS2PL. К правилу, использующемуся в базовом протоколе добавляется strict — все полученные замки на запись сохраняются до завершения транзакции и strong — все замки удерживаются до завершения транзакции. При этом, в СУБД есть несколько режимов блокировок. Выбор режима зависит от типа ресурса, который требуется заблокировать. На уровне строки или страницы можно применить блокировки следующих режимов:

  1. Разделяемая. Блокировка занимает ресурс только для чтения. Несколько транзакций могут блокировать этот ресурс таким образом и читать из него данные, но ни один процесс не сможет в него что-либо записать.
  2. Монопольная. Резервирует ресурс для выполнения операций INSERT, UPDATE, DELETE. На ресурс может быть установлена только одна блокировка такого типа, причем блокировки других режимов также не могут быть установлены вместе с ней.
  3. Обновления. Блокировка такого типа может быть установлена на ресурс, только если на нем еще не установлена другая обновляющая или монопольная блокировка. В случае установки ее на ресурс с разделяемой блокировкой, она накладывает на ресурс еще одну разделяемую блокировку. При этом, если модифицирующая объект транзакция подтверждается, то она преобразовывается в монопольную.

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

  1. Разделяемая с намерением. Защищает запрошенные или полученные разделяемые блокировки на некоторых ресурсах на более низком уровне иерархии.
  2. Монопольная с намерением. Защищает запрошенные или полученные монопольные блокировки на некоторых ресурсах на более низком уровне иерархии.
  3. Разделяемая с монопольным намерением. Защищает запрошенные или полученные совмещаемые блокировки на всех ресурсах более низкого уровня иерархии, а также блокировки с намерением на некоторых ресурсах более низкого уровня.

Проблемы 2PL

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

Одна из стратегий борьбы с тупиками, это их распознавание и обрыв. Для распознавания необходимо построить граф ожиданий, в котором вершины — транзакции, а дуги — запросы транзакции на блокировку, конфликтующую с уже установленной. Наличие контура в графе означает наличие тупика. Для разрешение взаимоблокировки нужно оборвать одну из транзакций из образующих контур. Такая стратегия применяется в MS SQL Server.

При этом может возникнуть новая проблема — голодание. Этим термином описывается ситуация, когда одна и та же транзакция становится жертвой разрешения тупика при каждом новом запуске. Для предотвращения этой проблемы можно использовать первоначальные отметки времени поступления транзакции при выборе жертвы. В SQL сервере вы можете присвоить параметру DEADLOCK_PRIORITY одно из 21 значений (от -10 до 10) для выбора разных уровней приоритета взаимоблокировки.

Кроме того, существует еще один способ борьбы с тупиками. Для преждевременного завершения транзакции можно установить ограничение по времени. Если время ожидания блокировки превышает это ограничение, транзакция обрывается. В SQL сервере используется следующий синтаксис: SET LOCK_TIMEOUT 4000

Snapshot Isolation

Snapshot Isolation — один из оптимистичных протоколов. Каждая транзакция читает из снапшота — состояния базы данных, на момент старта транзакции. При этом при выполнении транзакции формируется write set — все операции записи. Перед коммитом транзакции происходит проверка на то, пересекается ли write set с другими write set’ами, параллельно выполнявшихся транзакций. В случае пересечения с уже принятой транзакцией, текущая обрывается. Протокол справляется с наиболее тяжелыми из аномалий, но не обеспечивает сериализуемости даже по конечному состоянию.

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

Уровни изоляции в MS SQL Server

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

Read Uncommitted

Самая простая форма изоляции между транзакциями. Этот уровень не использует никакие блокировки, и, следовательно, совершенно не изолирует операции чтения от других транзакций. Из описанных в начале поста аномалий Read Uncommitted допускает три: грязное чтение, неповторяемое чтение и фантомы.

Read Committed

Существует две формы уровня изоляции Read Committed — для пессимистичной и оптимистичной моделей выполнения. В этом подразделе описывается пессимистичный вариант, оптимистичному соответствует уровень Read Committed Snapshot.

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

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

Repeatable Read

В отличие от предыдущего уровня изоляции, Repeatable Read вешает разделяемые блокировки на все считываемые данные и удерживает их до тех пор, пока транзакция не будет подтверждена или отменена. Поэтому в этом случае многократное выполнение запроса внутри транзакции всегда будет возвращать один и тот же результат. Недостатком этого уровня изоляции является дальнейшее ухудшение одновременного конкурентного доступа, поскольку период времени, в течение которого другие транзакции не могут обновлять те же самые данные, значительно дольше, чем в случае уровня Read Committed.

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

Serializable

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

Read Committed Snapshot

Последние два уровня используются в оптимистичном контексте. Read Committed Snapshot применяется на уровне инструкции, что означает, что что любая другая транзакция будет читать зафиксированные значения в том виде, в каком они существуют на момент начала этой инструкции. Для выборки строк для обновлений этот уровень изоляции возвращает версии строк в фактические данные и устанавливает на выбранных строках блокировки обновлений. Реальные строки данных, которые требуется изменить, получают монопольные блокировки.

Snapshot

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

Заключение

Сегодня мы познакомились с самыми основами теории транзакций и посмотрели, какие из них нашли свое применение в промышленной СУБД. Пост основывался на материалах лекций Новикова Б. А. и книге Душана Петковича Microsoft SQL Server 2012.

Что такое транзакция

Транзакция — это набор операций по работе с базой данных (БД), объединенных в одну атомарную пачку.

Транзакционные базы данных (базы, работающие через транзакции) выполняют требования ACID, которые обеспечивают безопасность данных. В том числе финансовых данных =) Поэтому разработчики их и выбирают.

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

Содержание

Что такое транзакция

Транзакция — это архив для запросов к базе. Он защищает ваши данные благодаря принципу «всё, или ничего».

Представьте, что вы решили послать другу 10 файликов в мессенджере. Какие есть варианты:

Кинуть каждый файлик отдельно.

Сложить их в архив и отправить архив.

Вроде бы разницы особой нет. Но что, если что-то пойдет не так? Соединение оборвется на середине, сервер уйдет в ребут или просто выдаст ошибку.

В первом случае ваш друг получит 9 файлов, но не получит один.

Во втором не получит ничего. Нет промежуточных состояний. Или получил всё, или не получил ничего. Но зато если произошла ошибка, вы снова перешлете сообщение. И друг получит все файлики разом, не придется проверять «не потерялся ли кто».

Казалось бы, ну недополучил файлик, что с того? А если это критично? Если это важные файлики? Например, для бухгалтерии. Потерял один файлик? Значит, допустил ошибку в отчете для налоговой. Значит, огребешь штраф и большие проблемы! Нет, спасибо, лучше файлы не терять!

И получается, что тебе надо уточнять у отправителя:

— Ты мне сколько файлов посылал?

— 10

— Да? У меня только 9. Давай искать, какой продолбался.

И сидите, сравниваете по названиям. А если файликов 100 и потеряно 2 штуки? А названия у них вовсе не «Отчет 1», «Отчет 2» и так далее, а «hfdslafebx63542437457822nfhgeopjgrev0000444666589.xml» и подобные. Уж лучше использовать архив! Тогда ты или точно всё получил, или не получил ничего и делаешь повторную попытку отправки.

Так вот! Транзакция — это тот же архив для запросов. Принцип «всё, или ничего». Или выполнены все запросы, которые разработчик упаковал в одну транзакцию, или ни один.

Допустим, вы переводите все деньги с одной карточки на другую. Выглядит это «внутри» системы как несколько операций:

delete from счет1 where счет = счет 1

insert into счет2 values (‘сумма’)

Принцип «всё или ничего» тут очень помогает. Было бы обидно, если бы деньги со счета1 списались, но на счет2 не поступили. Потому что соединение оборвалось или вы в номере счета опечатались и система выдала ошибку.

Но благодаря объединению запросов в транзакцию при возникновении ошибки зачисления мы откатываем и операцию списания. Деньги снова вернулись на счет 1!

Если говорить по-научному, то транзакция — упорядоченное множество операций, переводящих базу данных из одного согласованного состояния в другое. Согласованное состояние — это состояние, которое подходит под бизнес-логику системы. То есть у нас не остается отрицательный баланс после перевода денег, номер счета не «зависает в воздухе», не привязанный к человеку, и тому подобное.

Как отправить транзакцию

Чтобы обратиться к базе данных, сначала надо открыть соединение с ней. Это называется коннект (от англ. connection, соединение). Коннект — это просто труба, по которой мы посылаем запросы.

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

Выполнить все операции внутри.

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

Можно, конечно, каждый раз закрывать соединение с БД. И на каждое действие открывать новое. Но эффективнее переиспользовать текущие. Потому что создание нового коннекта — тяжелая операция, долгая.

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

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

Как открыть транзакцию

Зависит от базы данных. В Oracle транзакция открывается сама, по факту первой изменяющей операции. А в MySql надо явно писать «start transaction».

Как закрыть транзакцию

Тут есть 2 варианта:

COMMIT — подтверждаем все внесенные изменения;

ROLLBACK — откатываем их;

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

Например, я пишу запрос:

Запрос выполнен успешно, хорошо! Теперь, если я сделаю select из этой таблицы, прям тут же, под своим запросом — он находит Иванова! Я могу увидеть результат своего запроса.

Но! Если открыть графический интерфейс программы, никакого Иванова мы там не найдем. И даже если мы откроем новую вкладку в sql developer (или в другой программе, через которую вы подключаетесь к базе) и повторим там свой select — Иванова не будет.

А все потому, что я не сделала коммит, не применила изменения:

Я могу добавить кучу данных. Удалить полтаблицы. Изменить миллион строк. Но если я закрою вкладку sql developer, не сделав коммит, все эти изменения потеряются.

Когда я впервые столкнулась с базой на работе, я часто допускала такую ошибку: подправлю данные «на лету» для проведения теста, а в системе ничего не меняется! Почему? Потому что коммит сделать забыла.

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

Удалили. Делаем select count — посмотреть количество записей в таблице. А там вместо миллиона строк осталось 100 тысяч! Если база реальная, то это очень подозрительно. Врядли там было СТОЛЬКО тестовых записей.

Проверяем свой запрос, а мы там где-то ошиблись! Вместо «И» написали «ИЛИ», или как-то еще. Упс. Хорошо еще изменения применить не успели. Вместо коммита делаем rollback.

Тут может возникнуть вопрос — а зачем вообще нужен ROLLBACK? Ведь без коммита ничего не сохранится. Можно просто не делать его, и всё. Но тогда транзакция будет висеть в непонятном статусе. Потому что ее просто так никто кроме тебя не откатит.

Или другой вариант. Нафигачили изменений:

Но видим, что операцию надо отменять. Проверочный select заметил, что база стала неконсистентной. А мы решили «Ай, да ладно, коммит то не сделали? Значит, оно и не сохранится». И вернули соединение в пул.

Следующая операция бизнес-логики берет это самое соединение и продолжает в нем работать. А потом делает коммит. Этот коммит относился к тем 3 операциям, что были внутри текущей транзакции. Но мы закоммитили еще и 10 других — тех, что в прошлый раз откатить поленились. Тех, которые делают базу неконсистентной.

Так что лучше сразу сделайте откат. Здоровей система будет!

Итого

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

Одной операции всегда соответствует одна транзакция, но в рамках одной транзакции можно совершить несколько операций (например, несколько разных insert можно сделать, или изменить и удалить данные. ).

Чтобы отправить транзакцию к базе, нам нужно создать соединение с ней. Или переиспользовать уже существующее. Соединение называют также коннект (англ connection) — это просто труба, по которой отправляются запросы. У базы есть пул соединений — место, откуда можно взять любое и использовать, они там все свободные.

В некоторых системах транзакцию нужно открыть, в других она открывается сама. А вот закрыть ее нужно самостоятельно. Варианты:

COMMIT — подтверждаем все внесенные изменения;

ROLLBACK — откатываем их;

Делая комит, мы заканчиваем одну бизнес-операцию, и возвращаем коннект в пул без открытой транзакции. То есть просто освобождаем трубу для других. Следующая бизнес-операция берет эту трубу и фигачит в нее свои операции. Поэтому важно сделать rollback, если изменения сохранять не надо. Не откатите и вернете соединение в пул? Его возьмет кто-то другой и сделает коммит. Своих изменений, и ваших, неоткаченных.

Не путайте соединение с базой (коннект) и саму транзакцию. Коннект — это просто труба, операции (update, delete…) мы посылаем по трубе, старт транзакции и commit /rollback — это группировка операций в одну атомарную пачку.

См также:

Блокировки транзакций — что может пойти не так при одновременном редактировании

4.5. Транзакции. Механизм транзакций

Возможность отмены — только одно из свойств. Определение обычно дается очень обтекаемое, транзакция — последовательность операций с базой данных, логически выполняемая как единое целое. Транзакция обладает свойствами атомарности, согласованности, изоляции и долговременности (по-английски ACID — Atomicity, Consistency, Isolation, Durability).

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

Обратите внимание на смещение акцента в определении: «транзакция — это механизм. «! Именно от представления о транзакции как о механизме, выполняющем определенные функции, мы и будем отталкиваться в дальнейших рассуждениях.

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

Механизм транзакций обязательно используется для ВСЕХ операций в базе данных (о некоторых особых случаях будет рассказано ниже). Возможно, некоторые разработчики, пользующиеся высокоуровневыми инструментами разработки приложений баз данных, могут заявить, что они никогда не применяли транзакции и не видят в них нужды. Но это всего лишь означает, что всю работу по управлению транзакциями брал на себя инструмент разработки (и вряд ли он управлял ими достаточно эффективно!).

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

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

Хочется отметить, что начинающие пользователи часто ставят знак равенства между подтверждением транзакции и успешностью проведенных в ее рамках действий. На самом деле здесь нет четкой связи — решение о подтверждении транзакции принимается на основании логики клиентского приложения (проще говоря, зависит от произвола пользователя). Да, обычно если все операции в транзакции прошли успешно, то транзакция подтверждается и все полученные результаты «узакониваются», но такое поведение не является обязательным. Ведь ничто не должно мешать пользователю отменить результаты успешных операций, исходя из каких-то своих высших соображений. Пора разобраться в понятиях подтверждения («узаконивания») и отмены (отката) транзакций. Следующий раздел внесет ясность в этот вопрос.

Давайте углубимся в рассмотрение того, зачем нужны транзакции в базе данных. Пример с переводом денег дает верную подсказку, представляя транзакцию как некоторый черный ящик, в котором производятся действия над содержимым базы данных. В этот ящик нельзя заглянуть до того, как транзакция завершится подтверждением или откатом. Если бы сумели все-таки заглянуть «внутрь» транзакции, в контексте которой осуществляется перевод денег, то увидели бы печальную картину, что-то вроде того, что: деньги уже ушли с одного счета, но на другой не пришли, или, наоборот, пришли, но «размножились» и существуют сразу на обоих счетах. Другими словами, «внутри» транзакции база данных в какие-то моменты находится в неправильном с точки зрения бизнес-логики состоянии Такое неправильное состояние называется «нецелостным», а правильное соответственно — «целостным».

Целостное состояние базы данных — состояние, в котором база данных содержит корректную информацию, соответствующую правилам бизнес-логики, применяющимся в данном принижении

Отсюда следует еще одно определение транзакции:

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

Обратите внимание на слово «позволяющий»: оно подчеркивает потенциальность возможностей механизма транзакций по обеспечению целостности базы данных.

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

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

Давайте поясним эти мысли с помощью такого примера.

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

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

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

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

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

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

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

Механизм транзакций в InterBase

Надо сказать, что реализация транзакций в InterBase отличается от реализации транзакции в большинстве других СУБД. Это связано с особой архитектурой баз данных InterBase, именуемой Multi Generation Architecture (MGA) — многоверсионной архитектурой.

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

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

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

Заинтересованная транзакция — это транзакция, конкурирующая с текущей. Старейшая заинтересованная транзакция (oldest interesting transaction) — это старейшая транзакция, конкурирующая с текущей транзакцией.

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

Старейшая активная транзакция (oldest active transaction, OAT) — это транзакция, которая была активной в тот момент, когда запускалась самая старая из активных транзакций в момент запуска текущей.

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

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

Уровни изоляции транзакций

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

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

Должен или нет — это определяется уровнем изоляции.

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

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

Транзакции в InterBase могут иметь 3 основных возможных уровня изоляции: READ COMMITTED, SNAPSHOT и SNAPSHOT TABLE STABILITY. Каждый из этих трех уровней изоляции определяет правила видимости тех действий, которые выполняются другими транзакциями. Давайте рассмотрим уровни изоляции более подробно.

READ COMMITTED. Буквально переводится как «читать подтвержденные данные», однако это не совсем (точнее, не всегда) так. Уровень изоляции READ COMMITTED используется, когда мы желаем видеть все подтвержденные результаты параллельно выполняющихся (т. е. в рамках других транзакций) действий. Этот уровень изоляции гарантирует, что мы НЕ сможем прочитать неподтвержденные данные, измененные в других транзакциях, и делает ВОЗМОЖНЫМ прочитать подтвержденные данные.

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

SNAPSHOT TABLE STABILITY. Это уровень изоляции также создает «моментальный» снимок базы данных, но одновременно блокирует на запись данные, задействованные в операциях, выполняемые данной транзакцией. Это означает, что если транзакция SNAPSHOT TABLE STABILITY изменила данные в какой-нибудь таблице, то после этого данные в этой таблице уже не могут быть изменены в других параллельных транзакциях. Кроме того, транзакции с уровнем изоляции SNAPSHOT TABLE STABILITY не могут получить доступ к таблице, если данные в них уже изменяются в контексте других транзакций.

В первом разделе этой главы была сделана попытка рассмотреть механизм работы транзакций в СУБД InterBase в целом. Теперь необходимо рассмотреть практические аспекты применяющие транзакций в InterBase.

Программисты, использующие такие современные библиотеки для доступа к базам данных InterBase, как FIBPlus, IBProvider, IBX и IBObjects (см. главу «Обзор библиотек доступа к InterBase»), имеют возможность гибко управлять параметрами транзакций для получения наилучших результатов. Поэтому имеет смысл рассматривать параметры транзакций именно в интерпретации для этих библиотек.

Настройка параметров транзакции осуществляется с помощью перечисления набора констант, определяющих поведение транзакции, например, уровень изоляции. Эти константы пришли из InterBase API и имеют следующий вид: isc_tpb_read, isc_tpb_write, isc_tpb_ read_committed и т. д.

Обычно префикс isc_tpb_ опускается и константы для определения параметров транзакции пишутся без него.

Давайте рассмотрим значение и синтаксис применения каждой константы.

Виды параметров транзакции

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *