Ещё одна «засада» на уровне изоляции Read Uncommitted

добавлено: 27 окт 21
понравилось:0
просмотров: 708
комментов: 1

теги:

Автор: Александр Гладченко

По материалам статьи Craig Freedman: Query Failure with Read Uncommitted
Опубликовано 23 марта 2019 г., впервые опубликовано в MSDN 12 июня 2007 г.
В предыдущих статьях были рассмотрены практически все уровни изоляции, за исключением Read Uncommitted или NOLOCK. Эта статья завершает серию обсуждением того, что может приключиться, если читать данные ещё не зафиксированных транзакций. О вреде NOLOCK написано уже немало. Например, вы могли об этом почитать у Любора Коллара (Lubor Kollar) из «SQL Server Development Customer Advisory Team» и в (ныне уже недоступном) блоге Тони Роджерсона (Tony Rogerson).
В дополнение к многочисленным аргументам, ниже будет продемонстрирована еще одна опасность NOLOCK. Начнём с создания двух таблиц:
create table t1 (k int, data int)
insert t1 values (0, 0)
insert t1 values (1, 1)

create table t2 (pk int primary key)
insert t2 values (0)
insert t2 values (1)

Затем в первом сеансе заблокируйте первую строку t2, используя обновление, как показано ниже:
begin tran
update t2 set pk = pk where pk = 0

Теперь во втором сеансе выполните следующий запрос:
select *
from t1 with (NOLOCK)
where exists (select * from t2 where t1.k = t2.pk)

Этот запрос использует следующий план:
|--Nested Loops(Left Semi Join, WHERE:([t1].[k]=[t2].[pk]))
|--Table Scan(OBJECT:([t1]))
|--Clustered Index Scan(OBJECT:([t2].[PK__t2__71D1E811]))
Сканирование таблицы выбирает первую строку t1 без получения каких-либо блокировок, а затем пытается соединить эту строку с t2. Поскольку мы заблокировали первую строку в t2 и поскольку сканирование кластерного индекса t2 выполняется на уровне изоляции по умолчанию Read Committed, запрос блокируется.
Наконец, в первом сеансе удалите первую строку из t1 и зафиксируйте транзакцию:
delete t1 where k = 0
commit tran

Запрос во втором сеансе теперь можно продолжить. Однако мы только что удалили ту строку, к которой ему нужно было выполнить соединение, и чего он ждал, пока она была заблокирована. Поскольку запрос пытается получить данные из удаленной строки, он завершается ошибкой со следующим текстом:
Msg 601, Level 12, State 3, Line 1
Could not continue scan with NOLOCK due to data movement.
Как видно, сканирование с Read Uncommitted или сканирование с NOLOCK может привести не только к неверным результатам, но и даже к сбою запроса!
SQL Server 2000 также мог выдавать такую ошибку, если план запроса содержит Bookmark Lookup и строка удалялась после того, как она была возвращена поиском по некластеризованному индексу, но до того, как строка базовой таблицы была выбрана Bookmark Lookup. SQL Server 2005 при таком сценарии не выдает ошибки. Напомним, что в SQL Server 2005 оператор Bookmark Lookup это просто соединение. Таким образом, если Bookmark Lookup не может найти соответствующую строку базовой таблицы, он просто отбрасывает её, как и любое другое соединение.

Комментарии




Необходимо войти на сайт, чтобы оставлять комментарии