Транзакции. Параллельное исполнение. Уровни изоляции
В контексте баз данных параллельное исполнение возникает, когда два или более клиента пытаются обратиться к базе данных. При проектировании СУБД необходимо учесть проблемы, которые могут возникнуть при параллельной обработке транзакций. Транзакция — это логическая единица работы, в ходе которой может выполняться некоторый набор действий с объектами базы данных.
Очевидно, самое простое, что можно сделать — сделать исполнение всех наших запросов полностью упорядочиваемыми. К сожалению, у такого подхода есть большой минус — это слишком долго. Вследствие этого появились уровни изоляции, которые позволяют архитектору базы указать, при каких транзакциях нам действительно нужно, например, полное упорядочивание, а при каких нам будет выгоднее пожертовать некоторыми гарантиями для ускорения выполнения.
Проблемы при параллельной обработке транзакций
Косая запись
Аномалия "косой записи" — аномалия, которая возникает при ситуации когда мы из двух разных транзакций пытаемся изменить одни и те же данные (например, ячейки А и В) так, что общий первая транзакция затронет ячейку A, а вторая — ячейку В и мы получим неконсистентный результат.
Пример возникновения аномалии:
Было:
t_1 = 50 t_2 = 50 Δ = 60
Транзакция Т1:
if t_1 + t_2 ≥ Δ begin t_1 = t_1 − Δ end if
Транзакция Т2:
if t_1 + t_2 ≥ Δ begin t_2 = t_2 − Δ end if
Стало:
t_1 = -10 t_2 = -10
В таком примере видно, что мы не могли выполнить обе транзакции так, чтобы сохранился инвариант данных, следовательно мы получаем аномалию. При этом стоит отметить, что такая пара транзакций успешно завершится, поскольку каждая из них проверит неизменность данных только в тех ячейкх базы, которые были изменены в ходе транзакции (t_1 для T1, t_2 для T2).
Фантомная запись
Аномалия "фантомная запись" — это аномалия, которая возникает в том случае, когда одна и та же транзакция пытается повторно считать данные из какой-то таблицы, но между двумя этими считываниями какая-то другая транзакция занесла изменения в эту таблицу. В таком случае при повторном чтении мы увидим новые данные, которых не было ранее.
Неповторяемое чтение
Аномалия "неповторяемое чтение" — это аномалия, которая возникает при повторном чтении ячейки таблицы, в которую были между этими чтениями внесены изменения. Таким образом, при повторном чтении мы можем увидеть вовсе не те данные, что были там раньше.
Грязное чтение
Грязное чтение — это аномалия, которая возникает при чтении еще не зафиксированных другой транзакцией изменений. Таким образом, мы можем увидеть данные, которые еще даже не были помечены как внесенные в базу.
Уровни изоляции транзакций
Сводная таблица
Ниже приведена сводная таблица, где для каждого из существующих уровней изоляции указано, какие аномалии ему характерны:
Аномалия / Уровень | Serializable | Snapshot | RepeatableRead | ReadCommitted | ReadUncommitted | |
---|---|---|---|---|---|---|
Косая запись | - | + | + | + | + | |
Фантомная запись | - | - | + | + | + | |
Неповторяемое чтение | - | - | - | + | + | |
Грязное чтение | - | - | - | - | + |
Уровень изоляции Serializable
При этом уровне изоляции мы гарантируем полную упорядочиваемость всех совершаемых транзакций, вследствие чего не возникнет ни одна из аномалий, перечисленных выше. Пример создания такой транзакции:
create transaction isolation level serializable; -- your query goes here commit;
Уровень изоляции Snapshot
Название этого уровня изоляции на русский язык можно перевести как слепок, что, на самом деле, приводит к тому, что если две транзакции совершаются прааллельно, то каждой из них выдают свой "слепок" базы данных в какой-то определенный момент.
После выполнения всех операций со слепком базы данных (удаление/запись/изменение/чтение) все изменения "вливаются" в основную версию базы. Транзакция будет завершена успешно, если в основной версии базы данных к моменту окончания транзакции не было изменений ни в одной из ячеек базы, измененных в ходе транзакции, не было изменений за время ее выполнения. Таким образом, если две транзакции выполняли операции над разными частями базы данных, то конфликтов у нас не возникнет и соответствующее слияние произойдет безболезненно. Если же изменялись одни и те же данные, мы можем получить аномалию "косой записи" (см. выше).
Стоит отметить, что формально такого уровня изоляции нет в стандарте языка SQL.
Уровень изоляции Repeatable read
При данном уровне изоляции выполняется гарантия, что при повторном чтении одного и того же поля записи в базе мы будем получать одни и те же значения в ходе транзакции. Исключение составляют те изменения, которые мы сами внесли в базу.
В базах данных, которые реализуют изоляцию посредством блокировок обеспечение такого уровня изоляции будет выполняться за счет блокировки или отдельных записей в таблицах, или страниц в целом.
Тем не менее, можно заметить, что описанная выше гарантия ничего не говорит о том, что мы не увидим при повторном чтении новых данных в базе. То есть может возникнуть ситуация, когда таблица при повторном чтении увеличится в размере. Такая аномалия называется аномалией "фантомной записи" (см. выше).
Пример создания такой транзакции:
create transaction isolation repeatable read; -- your query goes here commit;
Уровень изоляции Read committed
При этом уровне изоляции существует гарантия, что мы увидим любые изменения, которые были зафиксированы другими транзакциями. При этом можно заметить, что если мы дважды читаем информацию из одной ячейки, то между этими чтениями другая транзакция могла внести свои изменения, что влечет за собой проблему "неповторяемого чтения".
Стоит отметить, что при таком уровне изоляции базе данных не нужно полностью брать блокировку на запись или таблицу — в этом случае можно использовать частичную блокировку записей или страниц.
Пример создания такой транзакции:
create transaction isolation read committed; -- your query goes here commit;
Уровень изоляции Read uncommitted
При таком уровне изоляции транзакций у нас вовсе отсутствуют какие-либо блокировки, следовательно мы ничего не можем гарантировать пользователю. При таком уровне изоляции пользователь увидит любые текущие данные в базе данных, в том числе, он сможет увидеть "граязные данные" — то етсь данные, которые еще не были зафиксированы ни одной из транзакций и впоследствии могут быть вовсе откачены.
Вследствие того, что идея изменять данные в базе основываясь на еще незакомиченных данных звучит совсем безумно, стандартом SQL прописано, что транзакции, работающие на уровне изоляции read uncommitted, могут только читать данные, но не изменять их.
Пример создания такой транзакции:
create transaction isolation read uncommitted; -- your query goes here commit;