Это соединение (Соединение 1) все еще удерживает совместную блокировку строки с товаром 2, поскольку на уровне изоляции repeatable read совместные блокировки удерживаются до конца транзакции. В окне Соединение 2 выполняется следующий программный код, чтобы попытаться модифицировать строку с товаром 2:
-- Соединение 2
UPDATE Production.Products
SET unitprice = unitprice + 1.00
WHERE productid = 2;
SELECT productid, unitprice
FROM Production.Products
WHERE productid = 2;
Попытка блокируется и результат не выдается (запрос обновляющего процесса на монопольную блокировку конфликтует с совместной блокировкой, предоставленной читающему процессу).
Если бы читающий процесс выполнялся на рассмотренных уровнях изоляции read uncommitted или read committed, в этот момент он не удерживал бы совместную блокировку, и попытка модификации строки завершилась бы успешно.
В Соединение 1 выполняем запрос и фиксируем транзакцию (Обратите внимание на использовании в этом запросе COMMIT TRAN без BEGIN TRAN):
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT productid, unitprice
FROM Production.Products
WHERE productid = 2;
COMMIT TRAN;
Результат:
Теперь транзакция читающего процесса зафиксирована, и совместная блокировка снята, модифицирующему процессу в окне Соединения 2 предоставлена монопольная блокировка, которой он дожидался, и процесс может модифицировать строку.
-- Соединение 2
UPDATE Production.Products
SET unitprice = unitprice + 1.00
WHERE productid = 2;
SELECT productid, unitprice
FROM Production.Products
WHERE productid = 2;
Выдается результат:
Еще одна ситуация, возникновению которой препятствует уровень изоляции REPEATABLE READ, но не более низкие уровни изоляции, называется потерянным обновлением.
Потеря обновления происходит, когда две транзакции читают значение, выполняют вычисления, основанные на том, что они прочли, а затем обновляют значение. Поскольку на уровнях изоляции более низких, чем REPEATABLE READ после считывания блокировка ресурса не сохраняется, обе транзакции могут обновить значение, и "победит" та из них, которая запишет обновление последней, переопределив значение, обновленное другой транзакцией.
На уровне изоляции REPEATABLE READ обе стороны сохраняют свои совместные блокировки после первого чтения, поэтому позже никто не может установить монопольную блокировку для обновления. Ситуация приводит к взаимоблокировке или тупику и конфликт обновления устраняется.
Уровень изоляции serializable.
Выполняясь на уровне изоляции REPEATABLE READ, читающие процессы сохраняют совместные блокировки до конца транзакции. Следовательно, гарантируется повторяемость результатов при считывании строк, которые были прочитаны первый раз в транзакции.
Однако эта транзакция блокирует ресурсы (например, строки), которые запрос нашел в процессе выполнения в первый раз, а не строки, которых там не было во время выполнения запроса. Следовательно, второе чтение в той же самой транзакции может также вернуть и новые строки.
Эти новые строки называют фантомамиили призраками, а подобные чтения — фантомными чтениям. Такая ситуация возникает, если между двумя чтениями другая транзакция добавляет новые строки, удовлетворяющие условию фильтра читающего запроса.
Для предотвращения фантомных считываний необходимо перейти к более высокому уровню изоляции serializable.
По большей части уровень изоляции serializable ведет себя так же, как и repeatable Read,а именно, для чтения он требует от читающего процесса получения совместной блокировки и сохранения ее до завершения транзакции.
Но уровень изоляции serializable добавляет еще один аспект — логически этот уровень изоляции заставляет Читающий процесс блокировать целый диапазон ключей (строк), которые удовлетворяют условию фильтрации запроса читающего процесса.
Это означает, что процесс блокирует не только существующие строки, удовлетворяющие условию фильтра запроса, но и будущие строки. Или более точно, он блокирует попытки других транзакций добавить строки, удовлетворяющие условию фильтра запроса читающей стороны.
Пример:
-- Соединение 1
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRAN
SELECT productid, productname, categoryid, unitprice
FROM Production.Products
WHERE categoryid = 1;
Результат:
В Соединении 2 выполняется добавление записи
INSERT INTO Production.Products
(productname, supplierid, categoryid,
unitprice, discontinued)
VALUES('Product ABCDE', 1, 1, 20.00, 0); --вставляется строка, соответствующая условию отбора в Соединении 1.
|