пʼятниця, 24 липня 2015 р.

Механизмы обеспечения транзакций



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

5.4.1 Механизмы DB2

В DB2 для обеспечения атомарности транзакций применяется "упреждающее протоколирование", при котором изменения в данных записываются в журнальный файл прежде, чем транзакция фиксируется. Изменения в данных производятся в журнальных файлах и лишь при фиксации транзакции переносятся в основную область данных СУБД. Журнал используется для повторения или отката транзакций в случае сбоя.
В основе механизма обеспечения изоляции DB2 лежат блокировки. Суть блокировки состоит в том, что если для выполнения транзакции требуется гарантия того, что определенный объект не будет изменен параллельно выполняющейся транзакцией, объект блокируется, то есть, запрещается доступ к нему из других транзакций.
В самом простом случае СУБД обеспечивает блокировки двух типов:
  • S-блокировка - разделяемая блокировка или блокировка чтения, допускающая совместный доступ к строке таблицы;
  • X-блокировка - эксклюзивная блокировка или блокировка записи, не допускающая совместного доступа к строке.
Прежде чем считать (select) какую-либо строку из таблицы транзакция должна установить для строки S-блокировку. Прежде чем обновить (update, delete, insert) строку таблицы транзакция должна установить для строки X-блокировку. Если для строки уже установлена блокировка, несовместимая с той, которую пытается установить наша транзакция, то попытка нашей транзакции отвергается, транзакция переводится в ожидание до того момента, пока ранее установленная блокировка не будет снята. Совместимость блокировок показана в следующей таблице.
  S  X 
 S данет
 X нетнет
Тот или иной режим изоляции определяется той или иной комбинацией проверки и установки блокировок чтения.
Для уменьшения объема проверяемых при любом доступе блокировок вводятся дополнительные типы блокировок, называемые блокировками намерения. Блокировки намерения накладываются на всю таблицу перед наложением S- или X-блокировки на строку таблицы. Основной набор блокировок намерения следующий (хотя в действительности DB2 этот набор несколько шире):
  • IS - блокировка намерения чтения. Накладывается на некоторую таблицу T и означает намерение блокировать некоторую входящую в T строку в режиме S-блокировки.
  • IX - блокировка намерения записи. Накладывается на некоторую таблицу T и означает намерение блокировать некоторую входящую в T строку в режиме X-блокировки.
  • SIX - блокировка чтения с намерением записи. Накладывается на некоторую таблицу T и означает разделяемую блокировку всей этой таблицы с намерением впоследствии блокировать какие-либо входящие в нее строки в режиме X-блокировок.
Совместимость блокировок с учетом блокировок намерения показана в следующей таблице:
 ISSIXSIXX
ISдадададанет
Sдаданетнетнет
IXданетданетнет
SIXданетнетнетнет
Xнетнетнетнетнет

5.4.2 Механизмы Oracle

Механизмы обеспечения транзакций в Oracle называют оптимистическими. При этом имеется в виду то, что СУБД предполагает, что вероятность отката транзакции и конфликта транзакций по доступу к данным невелика. Oracle использует журнал, в котором хранит информацию для повторения транзакций в случае сбоя и "сегмент отката". Если транзакция изменяет базу данных, выполненные изменения сразу заносятся в область данных, а старая версия данных сохраняется в сегменте отката. Блокировки чтения, таким образом не накладываются. Подробный протокол чтения данных приводится ниже:
  • Для каждой транзакции (или запроса) запоминается текущий системный номер (SCN - System Current Number). Чем позже начата транзакция, тем больше ее SCN.
  • При записи страницы данных на диск фиксируется SCN транзакции, производящей эту запись; этот SCN становится текущим системным номером страницы данных.
  • Если транзакция читает страницу данных, то SCN транзакции сравнивается с SCN читаемой страницы данных.
  • Если SCN страницы данных меньше или равен SCN транзакции, то транзакция читает эту страницу.
  • Если SCN страницы данных больше SCN транзакции, то это означает, что некоторая другая транзакция, начавшаяся позже данной, успела изменить или сейчас изменяет данные страницы. В этом случае транзакция данная просматривает журнал транзакций назад в поиске первой записи об изменении нужной страницы данных с SCN меньшим, чем SCN данной транзакции. Найдя такую запись, транзакция использует вариант данных страницы из сегмента отката.
Для транзакции READ ONLY сегмент отката даже не создается.
При изменении данных Oracle накладывает на измененную строку эксклюзивную блокировку.
Таким образом, существенная разница в поведении двух рассматриваемых СУБД состоит в следующем: при угрозе возникновения чтения нецелостных данных DB2 блокирует транзакцию, пытающуюся выполнить такое чтение; Oracle же предоставляет транзакции то последнее целостное значение данных, которое существовало на момент начала транзакции. Подход Oracle в этой связи называют многоверсионным, так как одновременно разные транзакции могут видеть разные версии данных. Многоверсионный подход уменьшает количество блокировок, но подход, основанный на блокировках, обеспечивает более согласованное представление данных. Между разработчиками СУБД ведутся ожесточенные дискуссии о превосходстве того или другого подходов, но независимые эксперты, как правило, не могут отдать предпочтения ни тому, ни другому. Следует отметить, что существуют приемы и правила разработки приложений, которые позволяют обеспечить большую согласованность данных в Oracle и меньшее количество блокировок в DB2.

5.4.3 Реализация сценариев

5.4.3.1 Потерянные изменения
В обеих СУБД, независимо от установленного уровня изоляции, действия по сценарию п.5.3.1. будут следующими:
  • после ввода оператора UPDATE на шаге 2 транзакция Т1 "зависнет" (заблокируется):
  • оператор шага 2 завершится только, когда будет введен оператор COMMIT в транзакции T1.
  • на шаге 5 будет выбрано:
   id         dat 
---------- -----------
         1         101
         2         110
         3         120
         4         130
5.4.3.2 Грязное чтение
В DB2 при уровне изоляции CS и выше транзакция транзакция T1 на шаге 2 заблокируется. Оператор этого шага завершится только тогда, когда в транзакции T2 будет введен оператор ROLLBACK или COMMIT. Если транзакция T2 будет зафиксирована, то в транзакции T1 на шаге 3 будет выбрано:
        id         dat 
---------- -----------
         1         101
         2         101
         3         120
         4         130
Если транзакция T2 откатится, то в транзакции T1 на шаге 3 будет выбрано:
        id         dat 
---------- -----------
         1         100
         2         100
         3         120
         4         130
В Oracle блокировок не будет. На шаге 3 будет выбрано:
        id         dat 
---------- -----------
         1         100
         2         100
         3         120
         4         130
Поскольку транзакция T1 не завершается до шага 5, то на шаге 5 будет выбрано исходное состояние таблицы. (Если транзакция T2 зафиксируется, а не откатится, то на шаге 5 будут отображены только изменения, выполненные в T2).
5.4.3.3 Неповторяющееся чтение
В DB2 при уровне изоляции RS и выше транзакция T2 будет заблокирована на шаге 2, пока в транзакции T1 не будет выполнен оператор COMMIT или ROLLBACK. Если зафиксировать транзакцию T1 на шаге 2, то на шаге 4 будут выбраны уже обновленные значения, но это будет уже другая транзакция.
В Oracle при уровне изоляции SERIALIZABLE на шагах 1 и 4 будет выбрано одно и то же:
        id         dat 
---------- -----------
         1         100
Но если ту же выборку повторить после фиксации транзакции T1, то будет выбрано:
        id         dat 
---------- -----------
         1         101
5.4.3.4 Фантом
В DB2 при уровне изоляции RR транзакция T2 будет заблокирована на шаге 2, пока в транзакции T1 не будет выполнен оператор COMMIT или ROLLBACK. Если завершить транзакцию T1 на шаге 2, то на шаге 4 будут выбраны уже обновленные значения, но это будет уже другая транзакция.
В Oracle при уровне изоляции SERIALIZABLE на шагах 1 и 4 будет выбрано:
        id         dat 
---------- -----------
         3         120
         4         130
      
Если ту же выборку повторить после фиксации транзакции T1, будет выбрано:
        id         dat 
---------- -----------
         3         120
         4         130
         5         150

5.4.4 Тупики

Поскольку обе СУБД используют блокировки записи для предотвращения, например, такого нежелательного эффекта, как потерянные изменения, возможно возникновение тупиков. Тупик может возникнуть в том случае, если транзакция T1 требует каких-то ресурсов, которые эксклюзивно блокированы транзакцией T2, а транзакция T2 требует ресурсов, которые эксклюзивно блокированы транзакцией T1. Сценарий возникновения тупика, например, следующий:
Транзакция T1ШагТранзакция T2
UPDATE example SET dat=101 WHERE id=11 
 2UPDATE example SET dat=112 WHERE id=2
UPDATE example SET dat=111 WHERE id=23 
 4UPDATE example SET dat=102 WHERE id=1
Транзакция T1 должна заблокироваться на шаге 3, а транзакция T2 - на шаге 4. Единственным способом "развязки" тупика является принудительное освобождение одной из транзакций удерживаемого ею критического ресурса.
Обе СУБД обнаруживают и развязывают тупики. Дойдя до зависания обеих транзакций на шагах 3-4, обе СУБД после некоторой временной выдержки развязывают тупик, но делают это несколько по-разному. DB2 принудительно откатывает одну из транзакций с сообщением об ошибке, вторая транзакция при этом разблокируется и завершается. Oracle откатывает только оператор UPDATE одной из транзакций с сообщением об ошибке. Вторая транзакция при этом продолжает оставаться заблокированной. Транзакция, в которой был выполнен откат оператора, теперь должна явным образом завершиться (зафиксироваться или откатиться), только после этого будет разблокирована другая транзакция.

5.4.5 Эскалация блокировок

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

Сценарии возникновения нежелательных эффектов (потерянные изменения, грязное чтение...)



Проиллюстрируем нежелательные эффекты, возникающие при параллельном выполнении транзакций на примере таблицы EXAMPLE. Мы предполагаем, что в начале выполнения каждого следующего пункта содержимое этой таблицы возвращается к исходному, а именно:
Таблица EXAMPLE
id INTEGERdat INTEGER
1100
2110
3120
4130

5.3.1 Потерянные изменения

Транзакция T1ШагТранзакция T2
UPDATE example SET dat=dat+1 WHERE id=11 
 2UPDATE example SET dat=dat+1 WHERE id=1
COMMIT3 
 4COMMIT
SELECT * FROM example5 
Если потерянные изменения допускаются, то сценарий выполнится без ошибок и блокировок. На шаге 5 будет выбрано:
        id         dat 
---------- -----------
         1         101
         2         110
         3         120
         4         130

5.3.2 Грязное чтение

Транзакция T1ШагТранзакция T2
 1UPDATE example SET dat=101 WHERE id=1
UPDATE example SET dat= (SELECT dat FROM example WHERE id=1) WHERE id=22 
SELECT * FROM example3 
 4ROLLBACK
 5SELECT * FROM example
Если грязное чтение допускается, то сценарий выполнится без ошибок и блокировок.
На шаге 3 будет выбрано:
        id         dat 
---------- -----------
         1         101
         2         101
         3         120
         4         130

На шаге 5 будет выбрано:
        id         dat 
---------- -----------
         1         100
         2         101
         3         120
         4         130

5.3.3 Неповторяющееся чтение

Транзакция T1ШагТранзакция T2
SELECT * FROM example WHERE id=11 
[COMMIT]2UPDATE example SET dat=101 WHERE id=1
 3COMMIT
SELECT * FROM example WHERE id=14 
COMMIT5 
Если неповторяющееся чтение допускается, то сценарий выполнится без ошибок и блокировок. Операцию COMMIT в транзакции T1 на шаге 2 выполнять не придется.
На шаге 1 будет выбрано:
        id         dat 
---------- -----------
         1         100
На шаге 4 будет выбрано:
        id         dat 
---------- -----------
         1         101
Если выполнить операцию COMMIT на шаге 2, то результаты будут те же, что и без нее, но здесь уже не будет эффекта неповторяющегося чтения, так как разные результаты будут прочитаны уже в разных транзакциях.

5.3.4 Фантом

Транзакция T1ШагТранзакция T2
SELECT * FROM example WHERE dat>1101 
[COMMIT]2INSERT INTO example VALUES(5,140)
 3COMMIT
SELECT * FROM example WHERE dat>1104 
COMMIT5 
Если допускаются фантомы, то сценарий выполнится без ошибок и блокировок. Операцию COMMIT в транзакции T1 на шаге 2 выполнять не придется.
На шаге 1 будет выбрано:
        id         dat 
---------- -----------
         3         120
         4         130

На шаге 4 будет выбрано:
        id         dat 
---------- -----------
         3         120
         4         130
         5         150

Понятие транзакции и операторы COMMIT и ROLLBACK



Транзакцией называется единица работы СУБД, то есть, такая последовательность операторов SQL, которая обрабатывается СУБД как единое целое. Транзакция характеризуется четырьмя основными свойствами, часто называемыми свойствами ACID:
  • атомарность (atomity) - транзакция является неделимой, она выполняется полностью или не выполняется вообще; если транзакция прерывается на середине, то база данных должна остаться в том состоянии, которое она имела до начала транзакции;
  • параллельность (concurrency) - эффект от параллельного выполнения нескольких транзакций должен быть таким же, как от их последовательного выполнения; выполняющиеся транзакции не должны накладываться друг на друга;
  • целостность (integrity) - транзакция переводит база данных из одного непротиворечивого (целостного) состояния в другое; в ходе выполнения транзакции база данных может временно пребывать в нецелостном состоянии;
  • долговременность (duration) - после того, как транзакция завершена и зафиксирована, результат ее выполнения гарантированно сохраняется в базе данных.
Объем транзакции может варьироваться от одного SQL-оператора до всех действий с базой данных, выполняемых приложением. В случае, если транзакции в приложении не определены явным образом, поведение приложения в этом отношении определяется состоянием режима AUTOCOMMIT. Когда этот режим выключен, все приложение составляет одну транзакцию (если в нем не задано явное управление транзакциями), которая завершается с завершением приложения. Когда же этот режим включен, каждый SQL-оператор в приложении выполняется как отдельная транзакция, даже если в приложении имеются операторы явного управления транзакциями. Оператор является минимальной единицей транзакции: некоторые операторы могут включать в себя сложные действия над множеством строк, но все эти операции СУБД выполняет как одну транзакцию.
При выключенном режиме AUTOCOMMIT приложение может само управлять разбиением выполняемых им действий на транзакции. Первый SQL-оператор, выполняемый в приложении, начинает новую транзакцию. Все последующие операторы продолжают эту транзакцию, пока не встретится оператор COMMIT или ROLLBACK.
Оператор фиксации - COMMIT - завершает текущую транзакцию и фиксирует ее результаты. После выполнения оператора COMMIT результаты транзакции гарантированно сохраняются в базе данных и начинается новая транзакция. Оператор отката - ROLLBACK - завершает текущую транзакцию "с откатом". После выполнения этого оператора восстанавливается то состояние базы данных, в котором она была перед началом транзакции, и начинается новая транзакция. При выполнении оператора COMMIT или ROLLBACK снимаются все наложенные в транзакции блокировки (о блокировках - см. ниже).
Стандартом SQL/92 не предусматриваются какие-либо дополнительные возможности операторов COMMIT и ROLLBACK, поэтому их стандартный синтаксис очень прост (см. рис.5.1).

Рисунок 5.1 - Операторы COMMIT и ROLLBACK
Обе наши СУБД предусматривают, так называемые, точки сохранения. Точка сохранения задается оператором SAVEPOINT, и в операторе ROLLBACK имеется возможность отката транзакции не к началу, а к указанной точке сохранения.
С учетом этой возможности синтаксис операторов SAVEPOINT и ROLLBACK в Oracle показан на рис. 5.2, а в DB2 (это новая возможность версии 7.1.) - на рис. 5.3.

Рисунок 5.2 - Операторы ROLLBACK и SAVEPOINT в Oracle

Рисунок 5.3 - Операторы ROLLBACK и SAVEPOINT в DB2
В Oracle откат транзакции к указанной точке сохранения безусловно снимает все блокировки, наложенные после точки сохранения. В DB2 эта возможность является выборочной, задаваемой при создании точки сохранения. Имена точек сохранения могут повторяться в транзакции (в DB2 может быть указано требование уникальности имени) если имена повторяются, то выполнение следующего оператора SAVEPOINT с тем же именем точки отменяет предыдущую точку сохранения.
Выше мы отметили, что внутри транзакции состояние базы данных в принципе может быть нецелостным. В Oracle в описании ограничений целостности может быть задано ключевое слово DEFERRED (отсроченный) - для ограничения, проверка которого откладывается до окончания транзакции. Отключение ограничений может выполняться также оператором SET CONSTRAINT, что соответствует стандарту SQL/92. В DB2 отключения ограничений целостности могут быть сделаны только явным образом - оператором SET INTEGRITY. Подробное описание этих возможностей содержится в [7, 9].

Уровни изоляции


Свойство параллельности является одним из наиболее важных свойств транзакций, которые обеспечивают промышленные базы данных. Современные промышленные СУБД обеспечивают параллельную работу с одними и теми же данными огромного количества пользователей. Так, СУБД DB2 была протестирована при одновременной работе 60 тыс. пользователей; точных данных по Oracle у нас нет, но и здесь речь идет о десятках тысяч пользователей. В таких условиях важно, чтобы параллельно выполняемые транзакции не накладывались, а были изолированы друг от друга.

Стандарт SQL/92 определяет уровни изоляции транзакций в многопользовательской системе через отсутствие таких аномалий доступа к базе данных, которые могут в конечном итоге угрожать целостности данных. В стандарте различаются следующие аномалии:
  • Потерянные изменения. Транзакция Т1 читает данные. Транзакция Т2 читает те же данные. Транзакция T1 на основании прочитанного значения вычисляет новое значение данных, записывает его в базу данных и завершается. Транзакция T2 на основании прочитанного значения вычисляет новое значение данных, записывает его в базу данных и завершается. В результате значение, записанное транзакцией Т2, "затрет" значение, записанное транзакцией Т1.
  • Грязное чтение. Транзакция Т1 изменяет некоторые данные, но еще не завершается. Транзакция Т2 читает эти же данные (с изменениями, внесенными транзакцией Т1) и принимает на их основе какие-то решения. Транзакция Т1 выполняет откат. В результате решение, принятое транзакцией Т2, основано на неверных данных.
  • Неповторяющееся чтение. Транзакция Т1 в ходе своего выполнения несколько раз читает одни и те же данные. Транзакция Т2 в интервалах между чтениями данных в транзакции Т1 изменяет эти данные и фиксируется. В результате оказывается, что чтения одних и тех же данных в транзакции Т1 дают разные результаты.
  • Фантом. Транзакция Т1 в ходе своего выполнения несколько раз выбирает множество строк по одним и тем же критериям. Транзакция Т2 в интервалах между выборками транзакции Т1 добавляет или удаляет строки или изменяет столбцы некоторых строк, используемых в критерии выборки, и фиксируется. В результате оказывается, что одинаковые запросы в транзакции Т1 выбирают разные множество строк.
Промышленные СУБД в том или ином объеме выполняют требования стандарта по дифференциации уровней изоляции, но при формально одном и том же уровне изоляции поведение транзакций может существенно различаться в разных СУБД.
Определение уровней изоляции в стандарте и в рассматриваемых нами СУБД сведено в таблицу:
Уровни изоляции SQL/92АномалииDB2Oracle
А1А2А3А4
READ UNCOMMITTEDнетдададаUNCOMMITTED READ (UR)-
READ COMMITTEDнетнетдадаCURSOR STABILITY (CS)READ COMMITTED
REPEATABLE READнетнетнетдаREAD STABILITY (RS)-
SERIALIZABLEнетнетнетнетREPEATABLE READ (RR)SERIALIZABLE
Аномалии:
    А1 - Потерянные изменения
    А3 - Неповторяющееся чтение
    А2 - Грязное чтение
    А4 - Фантом
Кроме названных, в Oracle имеется еще уровень изоляции READ ONLY - для только-читающих транзакций.
В Oracle требуемый уровень изоляции может быть установлен для сеанса соединения с базой данных - как одна из возможностей оператора ALTER SESSION или для отдельной транзакции оператором SET TRANSACTION. Синтаксис этих операторов показан на рис. 5.4.

Рисунок 5.4 - Операторы ALTER SESSION и SET TRANSACTION (Oracle)
В DB2 уровень изоляции устанавливается для приложения, и способы его установки различны для разных способов разработки приложений. При работе в среде DB2 Command Line Processor или DB2 Command Center уровень изоляции устанавливается перед соединением с базой данных оператором CHANGE ISOLATION:

Рисунок 5.5 - Операторы CHANGE ISOLATION (DB2)