Введение
Транзакции повсеместно применяются в современных enterprise системах, обеспечивая целостность данных даже в многопоточных средах. Так давайте начнем сначала с опеределения термина и контекста, в которым обычно применяются транзакции.
Транзакции — это группа операций на чтение/запись, выполняющихся только если все операции из группы успешно выполнены.
По сути транзакции характеризуются следующими четырьмя свойствами (также известными как ACID):
- Атомарность
- Консистентность
- Изоляция
- Долговечность
В реляционной БД каждое SQL выражение должно исполняться в пределах транзакции. Без явного определения границ транзакции БД будет использовать неявную транзакцию, которая покрывает каждое SQL выражение. Неявная транзакция начинается до того как выражение запустится и заканчивается (коммит или откат) после того как выражение выполнится.
Режим неявной транзакции известен так же как режим autocommit.
Для enterprise систем режим автокоммита следует избегать из-за серьезных проблем с производительностью, и данный режим не позволит включать группу множественных DML в единую атомарную «единицу работы».
Это очень важно понимать и далее мы обсудим каждое из свойств.
Атомарность
Атомарность позволяет объединить единые и независимые операции в «единицу работы», которая работает по принципу «все-или-ничего», успешно выполняющаяся только если все содержащиеся операции успешно выполнятся.
Транзакция может инкапсулировать изменение состояний. Транзакция должна всегда оставлять систему в консистентном состоянии, независимо от того сколько параллельных транзакций выполняются в любой промежуток времени.
Консистентность (согласованность)
Консистентность означает что все требования уникальности были соблюдены для каждой совершенной транзакции. Это подразумевает, что требования по всем ключам (primary и foreign key), типам данных, триггерам успешно пройдены и не было найдено нарушений требования уникальности.
Изоляция
Во время выполнения транзакции параллельные транзакции не должны оказывать влияние на её результат. Изоляция дает нам преимущество сокрытия нефинальных изменений состояния от внешнего мира, и так же неуспешные транзакции не должны никогда порушить состояние системы. Изоляция достигается через управление параллельным выполнением операций используя пессимистический или оптимистический механизм блокировки.
Долговечность
Успешная транзакция должна всегда изменять состояние системы и прежде чем закончить ее изменения состояния сохраняются в лог транзакций. Если Ваша система внезапно выключится или возникнет перебой с электричеством, тогда все незавершенные транзакции можно воспроизвести.
Для систем сообщений, как JMS, транзакции не являются обязательными. Именно по этой причине в спецификации есть режимы подтверждения вне транзакций.
Операции с файловой системой обычно не контролируются, но если бизнес-требования требуют транзакций для операций с файлами, Вы можете использовать инструмент, такой как XADisk.
В то время как для файловых систем и систем передачи сообщений использование транзакций опционально, то для БД контроль за транзакциями обязателен.
Уровни изолированности
Хотя некоторые СУБД предлагают MVCC, обычно управление параллельным выполнением операций достигается через блокировку. Но, как мы знаем, блокировка увеличивает долю сериализации выполняемого кода, влияя на параллелизацию.
Стандарты SQL определяют 4 уровня изолированности:
- READ_UNCOMMITTED
- READ_COMMITTED
- REPEATABLE_READ
- SERIALIZABLE
Все уровни, кроме SERIALIZABLE уровня подвержены аномалии данных (феномен), которые могут произойти согласно следующей схеме:
Isolation Level | Dirty read | Non-repeatable read | Phantom read |
READ_UNCOMMITTED — чтение незавершенных транзакций | да | да | да |
READ_COMMITTED — чтение завершенных транзакций | нет | да | да |
REPEATABLE_READ — повторяемое чтение | нет | нет | да |
SERIALIZABLE — последовательное чтение | нет | нет | нет |
Феномены
Давайте обсудим каждую из аномалий.
Dirty Read («Грязное» чтение)
«Грязное» чтение происходит когда транзакция может прочитать незаконченные изменения некоторой другой выполняемой транзакции. Явление случается из-за того что нет блокировки предотвращающей это. На изображении сверху можно увидеть что вторая транзакция использует неконсистентное значение первой транзакции, которая впоследствии подверглась «откату». Так как данный эффект возможен только при минимальном уровне изоляции, а по умолчанию используется более высокий уровень изоляции (READ COMMITTED), то в скрипте чтения данных уровень изоляции будет явно установлен как READ UNCOMMITTED. Если вернуть уровень изоляции по умолчанию (READ COMMITTED) для транзакции 2, то поведение поменяется.
Non-repeatable read (Неповторяющееся чтение)
Неповторяющееся чтение проявляется когда повторные чтения в рамках одной транзакции дают разные результаты из-за параллельных транзакций, которые обновили запись, которую мы читаем. Это нежелательно, так как мы в конечном итоге используем устаревшие данные. Это предотвращается размещением разделяемой блокировки (блокировка на чтение) на чтение записи на все протяжении выполнения текущей транзакции.
Транзакция 1 | Транзакция 2 | |
SET TRANSACTION ISOLATION LEVEL READ COMMITTED —SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRAN;SELECT Value FROM Table1 WHERE Id = 1;WAITFOR DELAY ’00:00:10′;SELECT Value FROM Table1 WHERE Id = 1; COMMIT; |
BEGIN TRAN;
UPDATE Table1 COMMIT TRAN; |
|
Результат для READ COMMITTED | Value = 1 Value = 42 |
Мгновенное выполнение |
Результат для REPEATABLE READ | Value = 1 Value = 1 |
Ожидание завершения транзакции 1 |
Phantom read (Фантомное чтение)
Фантомное чтение происходит когда вторая транзакция вставляет запись, которая подходит по критериям предыдущей выборки первой транзакции. В результате получится, что одни и те же выборки в первой транзакции дают разные множества строк.
Следовательно, мы используем устравшие данные, который могут оказать негативное влияние на работу системы. Это предотвращается с помощью использования блокировки по диапазону или блокировки предикатами.
Транзакция 1 | Транзакция 2 | |
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ —SET TRANSACTION ISOLATION LEVEL SERIALIZABLE BEGIN TRAN; SELECT * FROM Table1 WAITFOR DELAY ’00:00:10′ SELECT * FROM Table1 COMMIT; |
BEGIN TRAN;
INSERT INTO Table1 (Value) |
|
Результат для REPEATABLE READ: | — первый SELECT ID: 1; Value: 1 — второй SELECT ID: 1; Value: 1 ID: 2; Value: 100 |
Мгновенное выполнение |
Результат для SERIALIZABLE: | — первый SELECT ID: 1; Value: 1 — второй SELECT ID: 1; Value: 1 |
Ожидание завершения транзакции 1 |
Уровни изолированности по умолчанию
Даже если SQL стандарт обязывает использовать уровень изоляции SERIALIZABLE, большинство СУБД используют разный уровень по умолчанию.
СУБД | Уровень по умолчанию |
Oracle | READ_COMMITTED |
MySQL | REPEATABLE_READ |
Microsoft SQL Server | READ_COMMITTED |
PostgreSQL | READ_COMMITTED |
DB2 | CURSOR STABILITY |
Обычно, READ_COMMITED является правильным выбором, поскольку даже SERIALIZABLE не может защитить Вас от потери обновлений во время выполнения чтения/записей в разных транзакций и веб-реквестов. Вы должны принять во внимание эту информацию при принятии решения об уровне изоляции в требованиях enterprise систем.