17.1.23

SQL Server 2005. Snapshot - изоляция транзакций

По материалам статьи Narasimha Rao AV: Snapshot Isolation

Эта статья описывает разные уровни изоляции, использующиеся в SQL Server, а также освещает одно из новшеств следующей версии этой СУБД под кодовым название Yukon, Snapshot - изоляцию. Описание Snapshot – изоляции будет сопровождаться примерами, и некоторыми фактами, которые могут показаться неожиданными при использовании этого нового для SQL Server уровня изоляции транзакций.

Хронология:

Как Вы, наверное, знаете, в Yukon появился новый уровень изоляции транзакций, названный Snapshot - изоляцией. Разработчикам, которые знакомы с базами данных Oracle, уже известны аналогичные решения. Давайте, для начала, сделаем небольшой, краткий обзор существующих уровней изоляции транзакций в SQL Server 2000.

1. Read Uncommitted
2. Read Committed
3. Repeatable Read
4. Serializable

Уровни изоляции призваны обеспечить в СУБД правила параллелизма и последовательности работы с данными. Когда устанавливается уровень изоляции, множество пользователей, работающих с одними и теми же наборами данных (одни и те же значения данных в столбцах и строках таблицы), устанавливают блокировки или следуют основанным на установленном уровне изоляции правилам. По умолчанию, устанавливается изоляция Read Committed, и эта установка действует в рамках сеанса. Основной принцип состоит в том, что пишущая транзакция всегда блокирует читающие транзакции, если они имеют уровни изоляции выше её, исключая Read Uncommited. Когда установлен уровень Read Uncommited, пишущая транзакция не блокирует читающие, а читающие не блокируют запись. Таким образом, Вы имеете возможность составить запрос таким образом, что получите грязные данные, которые ещё не сохранены в базе данных, и этим будет нарушен принцип последовательности. Когда установлен Read Committed, прочитать можно только сохранённые данные. Но как только читающая транзакция завершит процесс чтения данных, даже если сама транзакция к этому моменту ещё не завершена, её блокировка уже не будет препятствовать изменениям в этих данных. При использовании Repeatable Read, когда в одной транзакции читаются порции данных, одни и те же данные будут считаться каждый раз, когда происходит чтение в этой транзакции. Поэтому, даже в моменты, когда чтение данных не выполняется, другие транзакции не смогут изменять данные, но они смогут осуществлять вставки новых данных в таблицу или в диапазоны данных, которые в этот момент не блокированы. Уровень Serializable идёт на шаг дальше по отношению Repeatable Read и защищает все другие блоки данных от вставок. Это называется предотвращением фантомных чтений.

[В начало]

Snapshot - изоляция

Представленная выше краткая хронология уровней изоляции SQL Server 2000 демонстрирует то, что кроме Read Uncommited, во всех других уровнях изоляции запись блокирует чтения. Не существует опций на сеансовом уровне, которые бы предписывали запрет установки блокировки пишущей транзакцией для читающих транзакций (кроме использования хинта NOLOCK). Когда транзакция читает данные, иногда допустимо обращаться к уже сохранённым данным, без учёта последних или текущих изменений данных, но зато при этом избежать блокировок. Чтобы реализовать такую возможность, идя на встречу пожеланиям пользователей, в Yukon введён новый уровень изоляции, названным уровнем Snapshot - изоляции. Когда для сеанса установлен этот уровень изоляции, читающие транзакции получают предыдущую копию данных.
Перед изучением того, как новый уровень изоляции реализован в Yukon, сделаем краткий обзор того, как включить эту возможность для баз данных.


ALTER DATABASE Database SET ALLOW_SNAPSHOT_ISOLATION ON;

По умолчанию эта опция выключена - OFF. Вы можете узнать состояние этой опции, запросив данные из sysdatabase.


Select name, snapshot_isolation_state, snapshot_isolation_state_desc from sys.databases

Использование Snapshot – изоляции будет разрешено (ON) только после того, как выполняющиеся в настоящее время транзакции будут завершены. До этих пор состояние этой опции будет находиться в промежуточном состоянии: Pending_On (или Pending_Off при попытке отключить опцию). Наряду с опцией ALLOW_SNAPSHOT_ISOLATION, Вы можете установить опцию READ_COMMITTED_SNAPSHOT на всю базу данных.


ALTER DATABASE Database SET READ_COMMITTED_SNAPSHOT ON;

Когда опция READ_COMMITTED_SNAPSHOT будет включена, читающие транзакции в сеансе с уровнем Snapshot - изоляции не будут иметь возможность установить общую блокировку ресурса. Вы можете проверить установку состояния READ_COMMITTED_SNAPSHOT, сделав запрос к таблице sysdatabases.


Select name, is_read_committed_snapshot_on from sys.databases

Этот запрос возвращает 0 или 1. После того, как Вы включите Snapshot - изоляцию на уровне базы данных, станет возможным использовать уровень Snapshot - изоляции в рамках сеанса, если для него будет установлена следующая опция:


SET TRANSACTION ISOLATION LEVEL SNAPSHOT

Интересный момент, на который стоит обратить здесь внимание, это то, что вышеупомянутая инструкция не выдаст ошибку, если Snapshot – изоляция не разрешена на уровне базы данных. Но это возвратит ошибку, когда в сеансе будет предпринята попытка исполнения любой DML (не DDL) инструкции.

Msg 3952, Level 16, State 1, Line 1
Transaction failed in database 'TestDbase' because the database does not allow snapshot isolation. Use ALTER DATABASE to allow snapshot isolation.

Давайте теперь посмотрим на примере, как работает Snapshot – изоляция. Создайте в базе данных простую таблицу с именем Transactions, скрипт создания которой представлен ниже:


Create Table Transactions
(
TranID int identity(1,1),
TranName nvarchar(1000)
)
Go
Insert Into Transactions Values('ExistingTran1');
Insert Into Transactions Values('ExistingTran2');
Insert Into Transactions Values('ExistingTran3');
Go

Установите для базы данных уровень Snapshot - изоляции, если Вы ещё этого не сделали.


ALTER DATABASE <<Database>> SET ALLOW_SNAPSHOT_ISOLATION ON;
GO
ALTER DATABASE <<Database>> SET READ_COMMITTED_SNAPSHOT ON;
GO

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


-- Для сеанса 1: Запустите этот скрипт первым
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
GO
BEGIN TRAN
GO
Update Transactions Set TranName = 'Tran1' Where TranName = 'ExistingTran1' 
GO 

-- Для сеанса 2
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
GO
BEGIN TRAN
GO
Select * from Transactions
GO

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


COMMIT TRAN
GO
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
GO
BEGIN TRAN
GO
Select * from Transactions
GO

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

Msg 3951, Level 16, State 1, Line 1
Transaction failed in database 'TestDbase' because the statement used snapshot isolation but the transaction did not start in snapshot isolation.

Еще один интересный момент, даже после завершения транзакции первого сеанса, Вы все еще будете видеть старые данные во втором сеансе со второй транзакцией. Как только Вы завершите второй сеанс (завершите транзакцию, выполните переподключение или установите SET TRANSACTION ISOLATION LEVEL READ COMMITTED) и исполните выборку снова, тогда в неё попадут изменённые данные. До выполнения этих действий, повторное исполнение выборки всегда будет возвращать не изменённые данные.

Совет: как только транзакция начинается с уровнем Snapshot – изоляции, запросы на выборку всегда будут возвращать одни и те же данные до момента окончания транзакции, и это не зависит от состояния других транзакций, работающих с данными.

[В начало]

Как это всё работает:

Как в Yukon реализована Snapshot – изоляция, это интересная тема для изучения. С этого момента автор предполагает, что для базы данных разрешена Snapshot – изоляция, если он специально не оговаривает иного. Каждой исполняемой транзакции присваивается порядковый номер операции. Когда в рамках транзакции изменяются данные, Yukon делает копию первоначальных данных в TempDB и хранит вместе с ними номер операции. Когда другой сеанс запрашивает те же самые данные, процессор запросов возвратит данные из TempDB. В TempDB могут быть сохранены несколько версий данных, если одни и те же данные изменялись несколько раз. Но процессор запросов будет возвращать те данные, номер операции которых наиболее близок номеру операции читающей транзакции. Это объясняет представленный выше совет. Если перефразировать этот совет в терминах транзакций, то считываемые из TempDB данные будут всегда читаться этой транзакцией из TempDB.
После установки для базы данных Snapshot – изоляции, каждая изменяемая строка получит дополнительный привесок в 14 байт, нужный для хранения порядкового номера операции. Кроме этого, ресурсы TempDB будут использованы для хранения разных версий данных, на основании их порядковых номеров операций. Концепцию маркировки каждой строки порядковым номером операции принято называть Версионность Строк.

Автор надеется, что эта статья дала основные понятия того, чем является Snapshot – изоляция, и что нужно для начала её использования. Ваши комментарии направляйте по адресу: avnrao@gmail.com

Комментариев нет:

Отправить комментарий