Статьи

NuoDB и уровни изоляции в терминах MVCC

Хорошие новости, лояльные читатели, Trek снова погрузился в транзакционную семантику NuoDB. Ура! В частности, я хотел бы обратить внимание читателей этого блога на значение традиционных уровней изоляции SQL в терминах MVCC. Некоторым из вас может быть интересно, что такое уровень изоляции. Это один из более скучных углов стандарта SQL, поэтому я начну с краткого обзора причин и уровней изоляции.

Уровни изоляции: быстрый и грязный обзор

Транзакционная семантика базы данных часто описывается с помощью удобной аббревиатуры: ACID. И полностью транзакционная система должна демонстрировать атомарность, согласованность, изоляцию и долговечность. Тем не менее, все эти свойства имеют стоимость. Иногда разработчик приложения хотел бы ослабить некоторые свойства A, C и I, если это означает увеличение производительности или упрощение приложения. Примером может служить приложение, которое имеет некоторую логику, чья работа заключается в обновлении статистики. В этом случае, скажем, необходимо обновить глобальный аккумулятор для измеренной задержки на стороне клиента и увеличить счетчик для числа клиентов. Все это может быть сделано с полной изоляцией моментальных снимков для каждой транзакции, однако они будут мешать друг другу. Поскольку сложение по своей сути коммутативно,разработчик приложения знает, что эти две операции не должны всегда работать с «точной» видимой версией. Они могут быть выполнены против «последней» версии. Это должно позволить двум транзакциям обновлять счетчики, не вызывая отмены транзакций. В «классическом» SQL существует 4 указанных уровня изоляции. Они в порядке возрастающей изоляции: READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE. ПроверьтеREPEATABLE_READ, SERIALIZABLE. ПроверьтеREPEATABLE_READ, SERIALIZABLE. Проверьте здесь  для углубленного обзора.

Если вы перешли по этой ссылке или знакомы с определением уровней изоляции в стандарте, вы можете вспомнить, что в нем не указаны версии или видимость. Это потому, что определение стандарта было в терминах базы данных на основе блокировки. Фактически, поведение записи для каждого из этих уровней изоляции определяется с точки зрения режима блокировки (совместно используемой и исключающей) и продолжительности. Это очень запутанно для разработчиков, работающих с базой данных MVCC, такой как NuoDB, потому что весь смысл MVCC заключался в том, чтобы устранить необходимость явной блокировки таблиц / строк. Но не волнуйтесь, потенциально разочарованный разработчик, остальная часть этого блога посвящена описанию этих уровней изоляции в терминах MVCC. А затем предоставляя полезные примеры!

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

NuoDB — это распределенная база данных MVCC. Поэтому обсуждение аномалий чтения и длительностей блокировок не является простым способом описания семантики уровня изоляции. С точки зрения MVCC все должно быть описано с точки зрения наглядности. С точки зрения NuoDB, есть две области видимости. Одна — это самая последняя подтвержденная версия записи (я назову ее MOST_RECENT), другая — версия записи, которая была самой последней зафиксированной в какой-то момент времени (я назову это SNAPSHOT_VERSION).

MOST_RECENT  видимость для любой строки — это самая последняя версия этой строки, которая была установлена ​​зафиксированной транзакцией. Поскольку NuoDB является распределенной системой, узел будет проинформирован о коммите узлом, который отвечал за совершающую транзакцию. Следовательно, существует своего рода временная асимметрия, когда некоторые узлы будут интерпретировать данную транзакцию как зафиксированную, а другие (которые еще не получили и не обработали сообщение фиксации) интерпретируют ее как активную в данный момент. Это означает, что версия MOST_RECENT не стабильна при вызовах, поскольку транзакции, выполняемые на удаленных узлах, могут переходить из активного состояния в состояние фиксации между вычислениями версий.

SNAPSHOT_VERSION  видимость полностью стабильна. Когда транзакция начинается, набор всех уже совершенных транзакций точно определяет набор видимых записей (для всей продолжительности этой транзакции). Это означает, что при изоляции моментального снимка транзакция никогда не увидит набор строк, возвращаемых при изменении SELECT от одного вызова к другому (за исключением случаев, когда эта транзакция обновила содержимое таблиц, из которых выбирается SELECT). Следствием этого является то, что возможно получить конфликты обновления, когда две транзакции в изоляции моментальных снимков пытаются обновить одну и ту же версию строки. И что единственным способом разрешения таких конфликтов может быть откат одной из конфликтующих транзакций («сброс» ее снимка).

Уровень изоляции семантики MVCC:

В таблице ниже приведены уровни изоляции, поддерживаемые NuoDB в соответствии с их поведением MVCC. Мы здесь, в подводном логове геодезических куполов NuoDB, решили назвать изоляцию моментального снимка CONSISTENT_READ, но это классическая изоляция моментального снимка. NuoDB точно не поддерживает REPEATABLE_READ, потому что изоляция моментального снимка строго сильнее, чем repeatable_read (repeatable_read разрешает фантомное чтение, изоляция моментального снимка предотвращает их).

Правила видимости уровня изоляции

Уровень изоляции Версия, прочитанная SELECT Версия написана UPDATE
READ_COMMITTED MOST_RECENT MOST_RECENT
CONSISTENT_READ СНАПШОТ СНАПШОТ
WRITE_COMMITTED СНАПШОТ MOST_RECENT

READ_COMMITTED  похож на то, что можно получить со многими продуктами NoSQL. Всегда возвращает самую последнюю версию. Для транзакций, которые не заботятся о глобальной согласованности чтения или которые выполняют строго коммутативные или идемпотентные операции, READ_COMMITTED предлагает минимальный уровень изоляции. Приложение с менее строгими требованиями согласованности может корректно работать в режиме READ_COMMITTED и достигать более высокой пропускной способности, чем при CONSISTENT_READ.

CONSISTENT_READ  — хорошая старая изоляция снимка. Это, пожалуй, самый разумный уровень изоляции для распределенной базы данных, поскольку он полностью изолирует транзакции от неконфликтного поведения записи на других узлах. Примечание: в NuoDB этот уровень изоляции также является тем, что вы получаете, если указали SERIALIZABLE (это не является необычным в базе данных MVCC). Хотя изоляция моментальных снимков не является стандартным уровнем изоляции, она поддерживается во многих базах данных. Фактически, каждая БД, использующая MVCC, сможет выполнять изоляцию моментальных снимков. Это сильнее, чем REPEATABLE_READ, но не так строго, как глобальная сериализуемость.

WRITE_COMMITTED  — уровень изоляции только для NuoDB. Он предназначен для использования в ситуациях, когда чтение должно быть стабильным, но обновления могут быть корректно выполнены для самой последней принятой версии. Пример транзакции обновления статистики может быть примером транзакции, которая может извлечь выгоду из WRITE_COMMITTED.

Пример 1: читает

Следующие примеры сделают эти правила видимости более конкретными. Для этих примеров предположим следующее начальное состояние:

SQL> create table t1 (f1 integer);
SQL> insert into t1 values (1), (3), (5), (7), (9);
SQL> select * from t1;
 F1  
 --- 
  1  
  3  
  5  
  7  
  9  

Важным моментом здесь является то, что транзакция в READ_COMMITTED сможет прочитать последнюю подтвержденную версию, поэтому в этом примере она увидела вставленное значение 2 ПОСЛЕ фиксации транзакции 1. Те транзакции с видимостью чтения снимка не увидят никаких изменений в выводе SELECT. Важно помнить, что транзакция 1 была зафиксирована до того, как какое-либо изменение стало видимым для других транзакций. NuoDB не разрешает так называемое грязное чтение. И это хорошо.

Пример 2. Конфликт обновлений

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

READ_COMMITTED / WRITE_COMMITTED:  Здесь транзакция 2 обнаружила обновления транзакции 1 и заблокировала ее в ожидании конечного состояния для транзакции 1. Когда транзакция 1 зафиксирована, транзакция 2 оценила предикат обновления с точки зрения самой последней зафиксированной версии, поскольку записи больше не было, где f1 = 3, обновление транзакции 2 обновлено без строк.

CONSISTENT_READ / Изоляция моментального снимка:  обновление вызывает конфликт обновлений в транзакции 2. Это связано с тем, что для транзакции 2 нет единого способа выбрать версию записи и обновить эту версию записи, не перекрывая обновление транзакции 1. Поэтому обновление не удастся, и клиент должен решить, что делать дальше.

Пример 3: неперекрывающиеся записи

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

Этот случай иллюстрирует основное различие между CONSISTENT_READ и другими уровнями изоляции. В CONSISTENT_READ, поскольку обновления не имеют общих строк, обе транзакции могут выполняться без согласования. С READ_COMMITTED и WRITE_COMMITTED, поскольку обновляемая версия зависит от самого последнего подтвержденного значения, любое обновление таблицы будет вынуждать блокировку прибывающей транзакции (транзакция 2) до завершения транзакции обновления (транзакция 1). Если обновление выполнено, то другая транзакция будет использовать обновленную версию строки. Если транзакция обновления отменяется, то другой транзакции придется использовать предыдущую версию строки. Этот выбор версий строки не может быть выполнен до завершения транзакции обновления.

Заключение :

Уровни изоляции — сложная тема. С одной стороны, они предназначены для того, чтобы позволить пользователю ослабить свойства согласованности системы, чтобы выжать некоторую производительность, и согласованность — это абсолютно  хорошая вещь., С другой стороны, они позволяют достаточно способному разработчику значительно повысить производительность приложения БД. Они являются частью стандарта, поэтому NuoDB их поддерживает. Из приведенных выше примеров я надеюсь, что значение фактических уровней изоляции достаточно понятно. Я надеюсь, что также ясно, что нет единой формулы для выбора уровня изоляции. В общем случае изоляция CONSISTENT_READ (снимок) абсолютно безопасна и, вероятно, идеально подходит для большинства приложений. Если транзакции не будут обновлять одни и те же строки все время, CONSISTENT_READ обычно является наиболее производительным вариантом, поскольку он не требует блокировки строк, которые не являются частью набора обновлений. Однако если в некоторых общих строках будет много конфликтов записи, лучшим вариантом может быть READ_COMMITTED или WRITE_COMMITTED.Надеюсь, вы нашли этот пост полезным и информативным. Оставайтесь с нами для получения дополнительной информации, особенно в том, что касается обработки ошибок и задержки обновления.