Уровни изоляции транзакций в SQL. Шпаргалка

Материал этой статьи послужил основой для одного из параграфов главы "Транзакции, изоляция и блокировки", входящей в книгу "СУБД для программиста. Базы данных изнутри". Для более глубокого понимания механизмов см. параграфы "Уровни SQL-92", "Блокировки", "Взаимные блокировки процессов (deadlock)", "Версии данных", "Проявления эффектов изоляции" и "Толстые транзакции".

Статья также была опубликована в журнале «Мир ПК», № 07, 2009

Истоки проблемы

Напомним, что транзакцией называется упорядоченное множество действий, которые выполняются или все вместе или не выполняются вовсе. В применении к SQL транзакция включает в себя один и более операторов языка. Уровни изоляций транзакций с разной степенью обеспечивают целостность данных при их одновременной обработке множеством процессов (пользователей). Наиболее простой вариант: один пользователь записывает информацию на уровне записи таблицы, другой пытается ее прочитать. При отсутствии изоляции второй пользователь может прочитать данные незавершенной транзакции. Ситуация усложняется, если изменениям (модификации, вставке, удалению) подвержена не одна запись, а несколько, возможно даже в разных таблицах.

Прежде чем приступить к экспериментам на практике давайте кратко перечислим особенности уровней изоляции согласно стандарту ANSI SQL-92.

Незавершенное (черновое) чтение (read uncommitted)

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

Подтвержденное чтение (read committed)

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

Повторяемое чтение (repeatable read)

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

Версионный срез (snapshot)

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

Сериализуемость (serializable)

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

Испытания

Рассмотрим поведение системы в типовых случаях на простом примере. Для проведения эксперимента воспользуемся СУБД MS SQL Server 2005 или 2008.

Подготовка

Создадим на сервере базу данных с названием Test и выполним на ней несколько SQL-скриптов. В версии 2005 появился механизм версионности, нам он также понадобится,
но вначале нужно включить его на нашей базе данных.

USE master
ALTER DATABASE Test SET ALLOW_SNAPSHOT_ISOLATION ON 
GO
USE test

Создаем "подопытные" таблицы для тестов и заполним их данными. Предположим, что мы собираем поступающую с датчиков информацию в таблицу DevicesData. Поле DeviceId содержит идентификатор устройства, а поле Value - последнее полученное значение.

CREATE TABLE DevicesData (
   DeviceId int not null, 
   Value    int not null,
   CONSTRAINT PK_DevicesData PRIMARY KEY (DeviceId)
)
GO

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

TRUNCATE TABLE DevicesData
DECLARE @n int 
SET @n = 999
DECLARE @List TABLE (n int)
WHILE @n >= 0 BEGIN
   INSERT INTO @List (n)
   SELECT @n
   SET @n = @n - 1
END
INSERT INTO DevicesData (DeviceId, Value)
SELECT A.n * 1000 + B.n, 0 
FROM @List A CROSS JOIN @List B
GO

Проверки

В SQL Server Management Studio откроем два окна для запросов к нашей базе данных Test.

Завершенное чтение (read committed)

Установим для каждого процесса уровень изоляции READ COMMITTED. В первом окне запустим процесс-писатель, который меняет значение поля Value у двух случайным образом выбранных записей в таблице. Первое значение увеличивается на 1, второе уменьшается на 1. Изначально все значения поля Value в таблице равны нулю, значит и их сумма также будет равно нулю. Таким образом, после завершения каждой транзакции сумма значений поля Value в таблице будет оставаться равной нулю. Во втором окне запустим процесс-читатель, подсчитывающий эту самую сумму значений поля Value.

Процесс 1 (писатель)

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
DECLARE @Id int
WHILE 1 = 1 BEGIN
   SET @Id = 500000 * rand()
   BEGIN TRANSACTION
      UPDATE DevicesData SET Value = Value + 1 WHERE DeviceId = @Id
      UPDATE DevicesData SET Value = Value - 1 WHERE DeviceId = 500000 + @Id
   COMMIT
   WAITFOR DELAY '00:00:00.100'
END

Процесс 2 (читатель)

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT SUM(Value) FROM DevicesData

-----------
         -1

Нетрудно убедиться, что выбранный уровень не обеспечивает логической целостности. Если при запущенном "Процесс 1" в другом окне вручную запустить несколько раз "Процесс 2", то возвращаемые выборкой значения будут отличаться от нуля. Если же "Процесс 1" прервать, то "Процесс 2" снова покажет нулевую сумму.

Повторяемое чтение (repeatable read)

Чтобы избежать подобной проблемы, повысим уровень до повторяемого чтения, установив REPEATABLE READ. Повторив наш предыдущий эксперимент, можно убедиться в этом: процесс-читатель всегда возвращает нулевую сумму. Если же посмотреть накладываемые сервером блокировки, то можно увидеть многочисленные разделяемые блокировки уровня страниц (page shared lock), по сути на запись блокируется вся таблица.

Процесс 1 (писатель)

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
DECLARE @Id int
WHILE 1 = 1 BEGIN
   SET @Id = 500000 * rand()
   BEGIN TRANSACTION
      UPDATE DevicesData SET Value = Value + 1 WHERE DeviceId = @Id
      UPDATE DevicesData SET Value = Value - 1 WHERE DeviceId = 500000 + @Id
   COMMIT
   WAITFOR DELAY '00:00:00.100'
END

Процесс 2 (читатель)

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRANSACTION
   SELECT SUM(Value) FROM DevicesData
   EXEC sp_lock
COMMIT

-----------
          0

Версионный срез (snapshot)

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

Снова повторяемое чтение и фантомы

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

Процесс 1 (читатель)

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRANSACTION 
   SELECT SUM(Value) FROM DevicesData WHERE DeviceId > 999000
   WAITFOR DELAY '00:00:03'
   SELECT SUM(Value) FROM DevicesData WHERE DeviceId > 999000
COMMIT

Процесс 2 (писатель)

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRANSACTION
   INSERT INTO DevicesData (DeviceId, Value) VALUES (1000000, 111)
   WAITFOR DELAY '00:00:00.100'
COMMIT

-----------
          0
 
-----------
        111

Чтобы не блокировать таблицу, запустим читателя (Процесс 1) на небольшом диапазоне записей, а в паузе между двумя чтениями этого диапазона запустим Процесс 2, добавляющий новую запись. В результате первая выборка вернет 0, а вторая - 111.

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

Процесс 1 (писатель)

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
DECLARE @SumVal int
BEGIN TRANSACTION 
   SELECT @SumVal = SUM(Value) FROM DevicesData WHERE DeviceId > 999000
   WAITFOR DELAY '00:00:03'
   IF (@SumVal) = 0
      INSERT INTO DevicesData (DeviceId, Value) VALUES (1000000, 111)
COMMIT 

Процесс 2 (писатель)

SET NOCOUNT	ON
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRANSACTION
   INSERT INTO DevicesData (DeviceId, Value) VALUES (1000000, 111)
   WAITFOR DELAY '00:00:00.100'
COMMIT

Msg 2627, Level 14, State 1, Line 18
Violation of PRIMARY KEY constraint 'PK__DevicesData__51BA1E3A'. Cannot insert duplicate key in object 'dbo.DevicesData'.
The statement has been terminated.

Сериализуемость (serializable) или полная изоляция

Уровень сериализуемости SERIALIZABLE гарантирует, что все затронутые в транзакции данные не будут изменены другими транзакциями. На этом уровне появление "фантомов" исключается, поэтому становятся возможными такие сложные конкурентные операции, как, например, добавление записей в диапазон значений ключа с предварительной проверкой целостности. На практике такая обработка требуется в учетных системах, когда условием добавления операции, например, списания товара со склада, является предварительная проверка неотрицательности его остатка по сумме всех предыдущих операций.

Если повторить наш предыдущий пример с уровнем SERIALIZABLE, то ошибка добавления записи возникнет уже в Процессе 2.

Краткий итог

Тема уровней изоляции транзакций является важнейшей при обработке данных в любой многопользовательской среде, одним из примеров которой является СУБД. Реляционные СУБД на уровне стандарта SQL 92 предоставляют разработчику несколько уровней, обладающих четко определенными характеристиками и поведением. Более высокие уровни изоляции уменьшают возможности параллельной обработки данных и повышают риск взаимной блокировки процессов. Поэтому корректное использование уровней в зависимости от задач приложений всегда является выбором разработчика в зависимости от требований к обеспечению логической целостности данных, к скорости и к возможности параллельной многопользовательской обработки.

Комментарии

Неточность

Стандарт ISO (ANSI) SQL-92 не содержит уровня изолированности snapshot. Стандартом 92-го года предусмотрено четыре уровня изолированности: "грязное чтение", зафиксированное чтение, повторяемое чтение и сериализуемость. Соответственно, оператор SET TRANSACTION по стандарту имеет вид:
SET TRANSACTION {ISOLATION LEVEL {READ UNCOMMIED | READ COMMITED | REPEATABLE READ | SERIALIZABLE} | {READ ONLY | READ WRITE} | {DIAGNOSTICS SIZE число условий}}.,..;
Уровень изолированности snapshot может поддерживаться только "версионными" СУБД и стандартом SQL-92 он не регламентирован.
К сожалению, версионность, как способ изолированности транзакций, вообще плохо рассмотрен в серьезной литературе и, тем более, не имеет хорошей реализации. В современных СУБД применяют ограниченную версионность: одна транзакция-"писатель" и произвольное количество транзакций-"читателей". В полном варианте (много "писателей" и много "читателей") возможности управления транзакциями значительно шире, в частности, возможно реализовать полноценный механизм вложенных транзакций, предложенный Эллиотом Моссом еще в 1981 г. (позже Грей и Рэйтер развили идеи Э. Мосса см., например, J. Gray, A. Reuter Transaction Processing).
В свое время (порядка 10 лет назад) я поднимал этот вопрос в переписке с Джо Селко (руководителем комитета по стандартизации SQL), но ответа на свой вопрос не получил. Дело в том, что SQL, как средство моделирования, должен позволять имитировать реальные действия, не накладывая излишних ограничений. В жизни довольно часто встречаются ситуации, когда руководителю представляют несколько вариантов дальнейших действий, но гарантированно(!) принят (зафиксирован) может быть только один из этих вариантов. Это и позволяет сделать полноценная версионность. Можно рассмотреть дальнейшее развитие событий с подверсиями основных версий, тогда станет необходимым механизм, предложенный Э. Моссом.

Изображение пользователя Serguei_Tarassov.

Не относится

Да, к стандарту SQL-92 версионные срезы не относится, они здесь приведены только как механизм, присутствующий во многих современных СУБД и SQL Server в частности.

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

Моральная старость...

разрешение коллизий - это из другой "оперы"... Существуют OLAP, системы поддержки принятия решений и т.п. Вот, на них и лежит задача "разрешения коллизий". Повторю, есть множество вариантов и одно(!) решение. Именно это решение и фиксируется, остальные обязаны быть отброшены. Понимаете? Суть - механизм "разрешения коллизий"... в реальной жизни. Если СУБД будут поддерживать сей механизм на уровне "ядра", то все эти OLAP с их проблемами, просто... отомрут за ненадобностью.
Вообще идея "вложенных транзакций" и, как ее решение, механизм версионности - дают очень интересные возможности, по моделированию реальных процессов. Подумайте на досуге...

Изображение пользователя Serguei_Tarassov.

О чем речь?

О чем речь? Я говорил о коллизиях в рамках обсуждаемой темы - изоляции транзакций (OLTP). Вы говорите о коллизиях в контексте OLAP. Какие могут быть коллизии в OLAP-базах, если данные туда закачиваются и после только читаются?

Анализ данных

Сдается мне, что мы с Вами (и не только с Вами...) различно смотрим на проблему... Вы смотрите на уровни изолированности, как на некую данность постулированную в стандарте SQL. Я же смотрю, как на одно из важных средств решения практических задач. То есть, с моей точки зрения, изолированность - это попытка смоделировать одновременную работу нескольких пользователей над одними наборами данных с минимальными конфликтами. Сама проблема (появление конфликтов) присутствует в реальной жизни, она не привнесена появлением СУБД.
Принятый механизм изолированности далеко не идеален и не позволяет решать целый ряд задач, связанных с многопользовательской обработкой данных.
Для решения этих (иных, новых) задач надо было либо пересматривать (в сторону расширения) существующий механизм, либо искать иные решения. В частности, был выбраны OLAP-хранилища. Они обрели популярность потому, что на оперативной БД блокировочной СУБД невозможно работать с аналитическими запросами. Поскольку в этом случае, значительная часть данных закрывается для изменений (во время обработки аналитических данных они (данные) не должны модифицироваться). С другой стороны, аналитические запросы, как правило исполняются достаточно долго. Как следствие, исчезает возможность оперативной работы с данными. Чтобы сохранить эту возможность, данные перекачиваются в OLAP-хранилище и уже там происходит (многомерный) анализ.
Поэтому связь между OLAP и изолированностью достаточно очевидна. Но проблема предстает в совершенно ином виде, если используется версионная СУБД...

Собственно, разговор ушел в сторону... я ведь только заметил, что snapshot в стандарте нет. А говорить о блокировках и версиях не стоит (IMHO), тема большая и мало кому интересная...

Изображение пользователя Serguei_Tarassov.

Не так

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

Основная же причина - нормализованные данные OLTP требуют долгого времени при аналитической обработке, характеризуюшейся агрегацией и многочисленными соединиениями (в том числе и по неключевым полям) больших массивов данных. Поэтому появились денормализованные базы (BW), оптимизированные под конкретные типы запросов, появились новые форматы, модели и языки запросов (MDX, DMX, ...).

Версионность никаким боком не относится к этому.

"За рыбу - деньги"

Причина появления OLAP не в коллизиях между писателями и читателями. Для решения этой проблемы достаточно поднять рядом второй сервер с периодической пакетной синхронизацией и развести конфликтующих.
Сервер пакетной синхронизации не в силах решать коллизии. Если запись заблокирована аналитической транзакцией, то все "писатели", требующие доступа к этой записи, будут либо отваливаться, либо ждать... накручивая вероятность deadlock...

Основная же причина - нормализованные данные OLTP требуют долгого времени при аналитической обработке, характеризуюшейся агрегацией и многочисленными соединиениями (в том числе и по неключевым полям) больших массивов данных.
Этот вопрос так много раз обсуждался... как-то неудобно снова ворошить... Мы различно смотрим на вопросы проектирования БД. У меня не возникает подобных проблем...

Изображение пользователя Serguei_Tarassov.

Сервер

Сервер пакетной синхронизации не в силах решать коллизии. Если запись заблокирована аналитической транзакцией, то все "писатели", требующие доступа к этой записи, будут либо отваливаться, либо ждать... накручивая вероятность deadlock...

Там нет никаких коллизий, потому что это выделенный сервер для аналитической обработки базы данных. Все транзакции - читающие, за исключением регулярной закачки обновлений данных. На этом принципе строится BW (business warehouse).

Проблема, как я уже сказал, в скорости обработки. Нормализованная и оптимизированная для OLTP база данных не является в общем случае оптимальной для OLAP. Далее - BW, многомерные OLAP-базы, Data mining-базы и т.д.

Истина, убитая в споре

Нормализованная и оптимизированная для OLTP база данных не является в общем случае оптимальной для OLAP. Далее - BW, многомерные OLAP-базы, Data mining-базы и т.д.
Мы никогда не поймем друг друга в этом вопросе. На мой взгляд данные должны быть представлены один раз и не должны куда-то "переливаться". Нормальная СУБД + нормальное проектирование = залог успеха.

Изображение пользователя Serguei_Tarassov.

Ловлю на слове

Вот простой пример. Есть OLTP-база, в ней таблица хоз.операций с необходимой детализацией (дата, товар, клиент, количество, стоимость). Для простоты рассматриваем только продажи. Таблица оперативная, записи добавляются примерно 80 тысяч/день. Аналитическая обработка (бюджетирование) требует статистики за последние 5 лет. В типовом аналитическом запросе многократно используются агрегации по разным уровням (в основном по периоду) типа SELECT SUM(), SELECT COUNT() и т.д. Агрегация на таблице с 200 млн. записей выполняется порядка 5-10 секунд (сервер достаточно мощный). Каждая операция, например, создание гипотезы на будущий период содержит несколько аналитических запросов в каждом из которых используется несколько агрегаций. То есть время выполнения исчисляется минутами.

Ваше решение?

... а слово не воробей...

Сергей, Вы вольно или невольно... представили свое решение. Если хотите услышать мое решение, то сформулируйте задачу. А пока я мало, что понял...
Зачем бюджетированию 5-летний период? Даже для страны бюджет составляется на год... Или на этом предприятии свой гос.план с пятилетками? И еще... бюджет составляется каждый день на пять лет вперед?..

Изображение пользователя Serguei_Tarassov.

Не сбивайтесь с сути

Слово не воробей

данные должны быть представлены один раз и не должны куда-то "переливаться". Нормальная СУБД + нормальное проектирование = залог успеха. (c) Александр Усов

Не сбивайтесь с сути.

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

Для справки. Горизонт планирования - год с разбивкой на ближайшие и последующие 6 месяцев. 5-летняя статистика нужна, чтобы аналитик-маркетолог мог выбрать для гипотезы наиболее походящий полугодовой период и его предшественников (минус год, минус два) в качестве основы. Например, 4 года назад была Олимпиада или группа товаров, снятая с продаж 2 года назад первыпускается под новым брендом, товар, продажи которого начились 4 года назад в Японии выводится в этом году на рынок Кореи и т.д.

Зубочистка

Не сбивайтесь с сути
Я не могу сбиться с сути, поскольку только пытаюсь ее найти в Ваших сообщениях... В своем сообщении я говорил : Нормальная СУБД + нормальное проектирование = залог успеха/ Дайте мне основу для проектирования. Ваше решение привело к тому, что Вы описали... Но я не могу по Вашему решению понять суть задачи. Глядя на зубочистку... не сразу поймешь, для чего она предназначена... Частное решение отражает взгляды разработчиков, в большей степени, чем суть задачи, которую они решали.
Меня мало интересует проблема оптимальной обработки 200 млн. записей, поскольку я понимаю (IMHO), что компьютеры появились... относительно недавно... И, я понимаю (IMHO again) диалектику количества и качества...

Изображение пользователя Serguei_Tarassov.

Хорошо

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

Про Фому и про Ерёму

Я говорю о культуре проектирования, а Вы о возможностях современных компьютеров... Хотя, конечно, изначально было ясно, что мы с Вами не поймем друг друга...

Изображение пользователя Serguei_Tarassov.

Не ново

Согласно вашим высказываниям, OLAP - это следствие несовершенства СУБД (блокировочных, в частности) и плохого проектирования. 10 лет прошло со времен дискуссии в ФИДО, где вам пытались объяснить некорректность сего заявления.

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

Группируйте и

Группируйте и выгружайте данные в отдельные структуры. Пытаться постоянно найти компромисс между оперативной и аналитической работой с данными рано или поздно поставит в тупик ( при увеличении объема данных и пользователей работающих с ними ).

Версионность к уроням изоляции не относится

По крайней мере snapshot, это никакая не версионность. Сергей применил не удачный термин.
Snapshot - это "моментальный срез". Этот уровень изоляции гарантирует, что процесс получит данные по состоянию на момент начала транзакции, не блокируя "писателей". Более ни чего он не гарантирует. А программист, пишущий процедуры модификации данных с таким уровнем изоляции, подлежит немедленному увольнению за проф. не пригодность.
Snapshot только для "читателей"!
Дискуссия ниже вывана "особенностями перевода" и более ни чем. То, что стороны не поняли друг друга - закономерно :)

Вы неправы.

Вы неправы. Версионность это альтернативный, по отношению к блокировке, способ обеспечить изолированность. Другое дело, что в стандарте SQL-92 (и большинстве учебников/пособий) уровни изолированности сформулированы для блокировочных СУБД.
Дискуссии версии vs. блокировки ведутся достаточно давно, но так уж сложилось, что большинство популярных СУБД было блокировочными, и, в следствие этого, обсуждения проходят в основном теоретической плоскости. А разбираться в теории мало кому интересно.

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

Блокировочные СУБД хорошо себя зарекомендовали на низких уровнях изолированности, но они работают очень неэффективно на высоких уровнях изолированности. Версионные СУБД, строго наоборот, - им трудно поддерживать низкие уровни изолированности, но они хорошо работают на высоких уровнях изолированности.

Дискуссия ниже вывана "особенностями перевода" и более ни чем. То, что стороны не поняли друг друга - закономерно :)
"Стороны поняли друг друга" более 10 лет назад, когда этот вопрос обсуждался в FIDO... :)

Алаверды :)

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

Например, аналитическая транзакция с таким уровнем изоляции (Snapshot - прим. SB) вполне может записывать расчетные данные.

Вы смелый человек, если готовы модифицировать БД, но основании знания о ее состоянии, которое неявляется актуальным. Нет ни какой гарантии, что Ваши модификации не протеворечат текущему состоянию БД, и нет возможности это проверить. "... там тебя ждут большие неприятности" (С) Котенок Гав.

Проекции

Вы же сами пишите "версионность это альтернативный, по отношению к блокировке, способ обеспечить изолированность". Так что ни какой это не уровень изоляции, а другой способ изоляции.
Это другой способ изоляции, который имеет свои уровни изолированности. Уровни изолированности одного способа давно отмапированы на другой способ... достаточно посмотреть соответствующую литературу. Да и сами способы изолированности не догма, а рекомендации стандарта. Например, IBM для своей DB2, ничуть не стесняясь предлагают свои "расширения" стандартных уровней.

Полезный

Полезный материал.
Предлагаю дополнить его примерами, когда процесы используют разные уровни изоляции. Например, snapshot и read commited.