1.1 Введение

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

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

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

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

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

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

Рассмотрим ситуации, в которых могут возникнуть эти проблемы.

1.2 Потерянное обновление

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

Предположим, что имеются две транзакции, которые выполняются одновременно:

Transaction 1 Transaction 2
UPDATE tbl1 SET f2=f2+20 WHERE f1=1; UPDATE tbl1 SET f2=f2+25 WHERE f1=1;

Две транзакции изменяют значение поля f2. После завершения значение увеличится на 45. Действия происходят в следующем порядке:

  1. Обе транзакции читают текущее состояние поля и не обязательно должны выполняться в точной физической одновременности. Достаточно того, что вторая по порядку операция чтения должна быть завершена до того, как другая транзакция запишет свои результаты. В этом случае обе транзакции могут параллельно использовать одно и то же поле и при этом не возникнет конфликтов.
  2. Обе операции увеличивают значение поля, добавляя по 20 и 25 соответственно к предыдущему прочитанному значению.
  3. Транзакции пытаются записать результат вычислений в f2. Однако, из-за ограничений, две записи делать одновременно невозможно. В результате, происходит ситуация, когда одна запись будет внесена в базу данных раньше, а другая позже. Однако, вторая запись стерет предыдущую, тем самым, перезаписывая результат первой.

По окончании обеих транзакций, значение поля f2 может увеличиться не на 45, как планировалось изначально, а на 20 или 25. Таким образом, одна из изменяющих данные транзакций всё же будет потеряна, хотя необходимость данной операции по-прежнему остаётся и значение поля f2 будет изменено в более существенной степени, чем было ожидаемо.

1.3 «Грязное» чтение

Чтение данных, которые были добавлены или изменены транзакциями, но потом отклонены (отменены).

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

Transaction 1 Transaction 2
UPDATE tbl1 SET f2=f2+1 WHERE f1=1;
SELECT f2 FROM tbl1 WHERE f1=1;
ROLLBACK WORK;

В транзакции 1 поле f2 изменяется, а затем в транзакции 2 извлекается это значение. После этого происходит откат транзакции 1. В результате значение, полученное второй транзакцией, будет другим, чем то, что хранится в базе данных.

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

При повторном чтении данные изменились. Представьте, что два приложения открыли разные транзакции и запустили следующие SQL-команды:

Transaction 1 Transaction 2
SELECT f2 FROM tbl1 WHERE f1=1;
UPDATE tbl1 SET f2=f2+3 WHERE f1=1;
COMMIT;
SELECT f2 FROM tbl1 WHERE f1=1;

В транзакции 2 выбирается значение из поля f2. Затем в транзакции 1 значение поля f2 изменяется. При повторном выборе значения из поля f2 в транзакции 2 можно получить другой результат. Это проблематично, когда данные читаются для того, чтобы изменить их частично и записать обратно в базу данных.

1.5 Чтение «фантомов»

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

Transaction 1 Transaction 2
SELECT SUM(f2) FROM tbl1;
INSERT INTO tbl1 (f1,f2) VALUES (15,20);
COMMIT;
SELECT SUM(f2) FROM tbl1;

В транзакции 2 выполняется SQL-оператор, использующий все значения поля f2. Затем в транзакции 1 добавляется новая строка. После этого, если повторно выполнить тот же SQL-оператор в транзакции 2, то получится иной результат. Такая ситуация называется чтением фантома. Оно отличается от обычного чтения потому, что изменение результата происходит не из-за изменения/удаления старых данных, а из-за появления новых (фантомных) данных.