Смекни!
smekni.com

Версионность в Yukon (стр. 2 из 5)

Read committed. Этот уровень изоляции несколько строже, чем в модели, основанной на блокировании. В блокировочнике, если читающая транзакция с уровнем изоляции read committed наткнется на уже измененный, но еще не зафиксированный объект, то она будет ожидать его изменения и прочитает уже измененные данные. В версионнике же читающий запрос, как правило, берет версию данных на момент начала запроса, поэтому выборка будет согласованной. При этом не важно, меняются ли данные в настоящий момент какой-либо посторонней транзакцией или нет. Однако повторный запрос тех же данных, если их успели поменять между двумя запросами, вернет уже измененные значения. Поэтому здесь можно наблюдать тот же самый эффект неповторяемого чтения, что и в блокировочном read committed. Изменения же данных при этом уровне изоляции в версионнике, в плане согласованности, мало чем отличаются от аналогичных действий блокировочника, так как изменяться должна все равно последняя зафиксированная версия данных, а не устаревшая копия. Однако небольшие отличия, о которых будет рассказано позднее, все же есть.

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

Snapshot. Этого уровня изоляции нет в классификации ANSI, он присутствует только в версионниках. Суть его в том, что при этом уровне изоляции обеспечивается согласованный срез данных на момент начала транзакции, никакие изменения, произошедшие после старта транзакции в ней не видны. Иными словами, делается то же самое, что и в Read committed, но не для одного запроса, а для транзакции в целом. Таким образом, исключаются и неповторимые чтения, и фантомные чтения. С записью данных при этом уровне изоляции все несколько сложнее, так как если транзакция при изменениях обнаруживает, что необходимые ей данные уже поменял кто-то другой, то ее приходится откатывать.

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

Реализация в Yukon

В Yukon версионность не является состоянием сервера в целом, она может быть включена для каждой базы в отдельности, причем по умолчанию версионность включена только для служебных БД master и msdb, и тестовой AdventureWorks.

Версионность включается с помощью нехитрой команды:

ALTER DATABASE database_name SET ALLOW_SNAPSHOT_ISOLATION ON

После ее выполнения сервер не сразу переключает базу в версионный режим, а переводит механизм поддержки версионности (snapshot isolation framework) в состояние PENDING_ON, поскольку в этот момент в базе могут быть активные транзакции. После завершения всех активных транзакций над базой производятся все необходимые изменения, механизм версионности для нее переводится в состояние ON, и появляется возможность выполнять версионные запросы. Обратное действие осуществляется также в два этапа, сначала БД переводится в состояние PENDING_OFF, а потом уже отключается механизм поддержки версионности.

Проверить состояние всех баз на сервере можно с помощью вот такого запроса:

SELECT name, snapshot_isolation_framework FROM sys.databases

Здесь «snapshot_isolation_framework» можетприниматьследующиезначения: 0 – OFF,1 – ON, 2 – PENDING_OFF, 3 – PENDING_ON.

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

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

ПРИМЕЧАНИЕВ том случае, если на странице данных не хватает места под дополнительные 14 байт на каждую запись, то во время перевода базы в «версионное» состояние данные будут автоматически перераспределены по страницам.

Все версии записи собираются в специальном хранилище (version store heaps), которое расположено в tempdb. При любом изменении записи предыдущая версия копируется в это хранилище.

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

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

Для оценки предполагаемого размера хранилища версий предлагается следующая формула: Size (KB) = частота обновления версий (Version generation rate) Kb/sec. * время выполнения самой длинной транзакции (the longest transaction time) sec.

Как это работает

Здесь опять-таки проще всего разобрать по очереди все уровни изоляции предлагаемые Yukon снизу вверх.

Read uncommitted

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

Read committed

С read committed всенесколькосложнее. После включения поддержки версионности все запросы на чтение при этом уровне изоляции автоматически начинают работать как версионные. То есть если транзакция при чтении натыкается на заблокированную запись, то она читает предыдущую версию этой записи из tempdb.

ПРИМЕЧАНИЕНа самом деле в alpha-версии Yukon для поддержки версионности при read committed необходимо включить специальный трейс-флаг (3970). Но Microsoft торжественно клянется, что в финальном продукте все будет происходить автоматически.

Можно провести простенький эксперимент. Пусть есть небольшая табличка tst в БД с поддержкой версионности, например, AdventureWork, созданная с помощью вот такого скрипта:

CREATE TABLE tst(x int, y int)GOINSERT INTO tst(x, y) VALUES(1, 5)INSERT INTO tst(x, y) VALUES(2, 4)INSERT INTO tst(x, y) VALUES(3, 3)INSERT INTO tst(x, y) VALUES(4, 2)INSERT INTO tst(x, y) VALUES(5, 1)

Сначала откроем новое подключение, откроем read committed-транзакцию и сделаем выборку, транзакцию при этом закрывать не будем.

SET TRANSACTION ISOLATION LEVEL READ COMMITTEDBEGIN TRAN SELECT * FROM tst WHERE x = 3

Получим то, что и ожидалось: x = 3, y = 3.

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

BEGIN TRAN UPDATE tst SET y = -1 WHERE x = 3

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

Тип Описание Объект Режим Статус spid
TAB 1963154039 IX GRANT 52
RID 1:1357:2 72057594057326592 X GRANT 52
PAG 1:1357 72057594057326592 IX GRANT 52

Таблица 1

То есть картина совпадает с той, которую можно видеть при использовании предыдущей версии SQL Server или БД без поддержки версионности.

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

SELECT * FROM tst WHERE x = 3

И результат будет точно таким же: x=3, y=3. Если попробовать сделать то же самое на БД без включенной поддержки версионности, то второй запрос из первой транзакции не выполнится. Он будет ожидать фиксации или отката второй транзакции. То есть он попросту не сможет прочитать нужную запись, поскольку она заблокирована. Но в данном случае блокировка нисколько не мешает прочитать версию данных, существовавшую на момент начала выборки.

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

Если теперь зафиксировать изменения тестовой таблицы, произведенные второй транзакцией:

COMMIT TRAN

и сделать опять выборку тех же данных в транзакции номер один: