Версионность в Yukon

Информация - Компьютеры, программирование

Другие материалы по предмету Компьютеры, программирование

?елается версионное сканирование таблицы, и выясняется, какие записи необходимо изменить.

Предпринимаются попытки изменить отобранные записи.

Если запись изменилась с момента версионного скана, то проверяется, не перестала ли она удовлетворять критерию отбора, и если не перестала, то запись меняется, если перестала, то пропускается. (Некоторые реализации применяют здесь более хитрые алгоритмы, но этот вопрос выходит за рамки данной статьи).

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

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

Чистый же блокировочник работает немного по другому сценарию. Сканирование данных ему не имеет смысла делать, так как все запросы на чтение у него блокирующие. Поэтому он просто перебирает все записи в таблице по очереди (напомню, речь идет о таблице без индексов), проверяя их на соответствие условию выборки, и накладывая при этом блокировку обновления (update lock). Такая блокировка совместима с блокировками чтения, но несовместима сама с собой и с монопольными блокировками. Таким образом, читающим запросам подобный перебор не мешает, но другие блокировки обновления и монопольные будут помехой этому запросу. Следовательно, если в момент перебора в таблице монопольно заблокирована хотя бы одна запись (что и имеет место в данном примере, так как запись была изменена, но транзакция еще не зафиксирована), то рано или поздно изменяющий запрос до нее доберется и зависнет на блокировке, ожидая фиксации вражеской транзакции.

Несмотря на возможность версионных запросов, Yukon все равно при записи данных поступает как блокировочник, что и приводит к вышеописанному эффекту.

Repeatable read

Уровень изоляции repeatable read в базе с включенной поддержкой версионности работает точно так же, как и на базе без оной. Совершенно спокойно накладываются и удерживаются должное время все положенные по статусу разделяемые (share) блокировки. Да в общем-то, вряд ли тут вообще что-то могло измениться. Но появилась одна полезная возможность: Если запрос выполняется по базе с включенной поддержкой версионности, то при указании оптимизатору хинта READCOMMITTED в читающем запросе, выборка будет версионной. Возможность действительно довольно полезная - в связи с некоторыми особенностями уровня изоляции snapshot.

Snapshot

Уровень изоляции snapshot, является чисто версионным, в отличие от предыдущего, чисто блокировочного, и вообще совершенно новым для SQL Server.

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

BEGIN TRAN

UPDATE tst SET y = 2 WHERE x = 4А в другом подключении начать snapshot транзакцию с читающим запросом к той же табличке…

SET TRANSACTION ISOLATION LEVEL SNAPSHOT

BEGIN TRAN

SELECT * FROM tstТо snapshot-транзакция, как, впрочем, и версионный read committed, совершенно спокойно отработает, вернув предыдущее значение измененной записи. Однако если сейчас зафиксировать первую, изменяющую транзакцию…

COMMIT TRANА затем повторить ту же самую выборку из snapshot транзакции…

SELECT * FROM tstТо эта выборка вернет все еще старые значения записей, существовавшие до фиксации первой транзакции. То есть здесь чтение полностью воспроизводимо, в отличие от read committed. Более того, этот уровень изоляции не допускает появления фантомов, в отличие от блокировочного repeatable read. Например, если выполнить третью транзакцию, в которой в таблицу добавляются записи…

BEGIN TRAN

INSERT INTO tst (x, y) VALUES (6, 0)

COMMIT TRANТо очередная выборка всех записей таблицы tst из snapshot-транзакции вернет все те же записи, что и в первый раз, просто потому, что на момент первого запроса нового значения еще не было. В общем, с таблицей можно делать все что угодно, все выборки из snapshot-транзакции будут одними и теми же.

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

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

И в данном случае Yukon мало чем отличается от версионника. Если при уровне изоляции read committed в случае изменения он может себе позволить вести себя как блокировочник, то при у