Перейти к содержанию

Изолированность транзакций

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

Таблица поведения уровней изоляции

Где стоит знак + - есть механизм предотвращения проблемы.

Уровень\Проблема LOST UPDATE DIRTY READ NON REPEATABLE READ PHANTOM READS
READ UNCOMMITTED + - - -
READ COMMITED + + - -
REPEATABLE READ + + + -
SERIALIZABLE + + + +

Проблемы паралельного доступа

Lost Update

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

  1. У нас есть таблица tbl с колонкой A = 0 и id = 1.
  2. Транзакция 1 читает и обновляет поле: A = A + 10 (UPDATE tbl SET A=A+10 WHERE id=1;).
  3. Транзакция 2 читает и обновляет поле: A = A + 20 (UPDATE tbl SET A=A+20 WHERE id=1;).

В обеих транзакциях меняется поле А, и по завершению обеих транзакций A должно быть 30. Но может получиться так, что значение может быть либо 10, либо 20, потому что одна из транзакций перезаписала предыдущую. То есть получился следующий флоу:

  1. Обе транзакции читают текущее состояние поля A.
  2. Обе транзакции вычисляют состояние поля А (первая добавляет 10, другая - 20).
  3. Транзакции пытаются записать значение. Но так как физически просто невозможно выполнить 2 записи, то какая-то из транзакций выполнится первой, а какая-то последней.

Dirty read

Dirty read - чтение данных, добавленных или измененных транзакцией которая в последствии откатится.
Например, у нас снова есть таблица как и выше, только 1 транзакция будет пытаться получить данные, а другая - их записать, но изменения фиксировать не будет:

  1. Транзакция 1 изменяет данные которые нужны транзакции 2, но не фиксирует (commit) изменения. (UPDATE tbl SET A=A+10 WHERE id=1;, A = 10)
  2. Транзакция 2 читает данные из БД, и получает в том числе и те, что поменяла транзакция 1, но не зафиксировала изменения. (SELECT A FROM tbl WHERE id=1, A = 10.)
  3. Транзакция 1 откатывает изменения (ROLLBACK, A = 0).
  4. Транзакция 2 завершается с неверными данными (A = 10).

Non-repeatable read

Non-repeatable read - ситуация, при которой при повторном чтении данных в транзакции ранее прочитанные данные оказываются измененными. Например:

  1. Транзакция 1 выбирает данные (SELECT A FROM tbl WHERE id=1, A = 0)
  2. Транзакция 2 меняет данные и делает commit (UPDATE tbl SET A=A+10 WHERE id=1; COMMIT;, A = 10)
  3. Транзакция 1 снова выбирает данные (SELECT A FROM tbl WHERE id=1, A = 10)

Phantom reads

Phantom reads - проблема, при которой транзакция несколько раз выбирает множество строк по одним и тем же критериям, в последствии получая разное количество данных. Например:

  1. Транзакция 1 получает сумму всех A (SELECT SUM(A) FROM tbl, sum = 0)
  2. Транзакция 2 добавляет ещё одну запись с A = 15 и комитит её (INSERT INTO tbl (A) VALUES (15); COMMIT)
  3. Транзакция 1 снова получает сумму всех A (SELECT SUM(A) FROM tbl, sum = 15)

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

Под уровнем изоляции транзакций понимается степень защиты от вышеперечисленных видов несогласованности данных, которые возникают при выполнении параллельных транзакций. Стандарт SQL-92 определяет 4 их уровня.

Read uncommited

Самый низший, первый уровень изоляции. Имеет самую плохую согласованность данных, но и самую высокую скорость выполнения транзакций. Каждая транзакция при данном уровне изоляции видит незафиксированные изменения другой транзакции (Dirty read).

На данном уровне изоляции нельзя использовать данные, на основе которых происходят какие-то важные бизнес-процессы. Такие данные можно использовать для примерных расчётов чего либо.

Типичный способ реализации - блокировка данных на время выполнения изменения транзакцией.

Read commited

При этом уровне изоляции транзакции видят только зафиксированные изменения из других транзакций, таким образом уровень обеспечивает защиту от dirty read. Но из-за этого он так же получает проблему non-repeateble read, так как транзакция будет видеть обновленные строки.

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

Repeatable read

Этот уровень позволяет предотвратить проблему non-repeatable read, т.е. мы не сможем увидеть в читающей транзакции изменения данных. Проблема предотвращается путем блокировки читающей транзакцией данных которыми она пользуется - любая пишущая транзакция наткнется на блокировку и будет ждать конца исполнения читающей транзакции.
Однако, другие транзакции могут вставлять новые строки и читающая транзакция при повторной выборке данных по тому же условию получит их, что приведет к проблеме phantom reads.

Стоит учесть, что проблема фантомного чтения на данном уровне изоляции не появляется в PostgreSQL.

Serializable

Самый высокий уровень изолированности транзакций, при котором транзакции полностью изолируются друг от друга. Результат выполнения в данном случае будет таким, как будто транзакции выполнялись последовательно, что решает проблему phantom reads.

Ссылки