Deadlocks

Существуют и более удачный способ определения взаимоблокировок (хотя и более трудоемкий). Для этого менеджер блокировок строит направленный граф, который называется

Deadlocks

Информация

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

Другие материалы по предмету

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

Сдать работу со 100% гаранией
ается.

«ранение-ожидание» (wound-wait). Если транзакция T1 «старше» T2, тогда T1 «ранит» T2; ранение обычно носит «смертельный» характер транзакция Т2 откатывается, если только к моменту получения «ранения» T2 не оказывается уже завершенной. В этом случае Т2 «выживает» и отката не происходит. Если же Т1 «младше» Т2, тогда Т1 разрешается находиться в состоянии ожидания на блокировке.

Преимущества этого способа заключаются в первую очередь в том, что взаимоблокировки не допускаются в принципе. Этот способ несколько проще в реализации, нежели построение и отслеживание графа ожидания. И, наконец, отсутствует вероятность циклического рестарта отмененной транзакции, так как при откате временная метка сохраняется, и любая транзакция со временем гарантировано станет самой «старшей», а значит, ее не откатят.

Недостаток же этого способа заключается в том, что число откатов здесь гораздо больше, чем в реализации на основе графа ожидания.

Реализация в Microsoft SQL Server

В Microsoft SQL Server используется механизм устранения взаимоблокировок на основе графа ожидания. Граф строится при каждом запросе блокировки. По истечении некоего тайм-аута просыпается монитор блокировок, и если он обнаруживает, что какая-то транзакция ждет слишком долго, инициируется процесс нахождения замкнутого цикла в графе ожидания. В случае обнаружения мертвой блокировки происходит откат одной из транзакций, участвующих в цикле. «Жертва» вычисляется в зависимости от объема проделанной работы, которая в свою очередь определяется по количеству записей в журнале транзакций, которые необходимо откатить. Однако есть возможность указать серверу, какую транзакцию предпочтительнее видеть в качестве «жертвы», с помощью команды:

SET DEADLOCK_PRIORITY { LOW | NORMAL | @deadlock_var }Здесь @deadlock_var переменная в диапазоне от 1 до 12, чем меньше число, тем ниже приоритет; LOW соответствует 3, а NORMAL 6.

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

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

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

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

Возможные причины возникновения взаимоблокировок

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

Строго говоря, все случаи взаимоблокировки сводятся к нарушению порядка доступа к объектам. Далее разберем несколько примеров транзакций, потенциально способных привести к тупиковой ситуации. Но перед этим, чтобы примеры были более наглядными, надо выбрать базу для экспериментов (например стандартную Northwind) и создать в ней табличку для дальнейших опытов. Для этого достаточно выполнить в Query Analyzerе вот такой скрипт:

--- Выбор тестовой базы

USE Northwind

GO

 

--- Создание таблицы

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Tbl]')

and OBJECTPROPERTY(id, N'IsUserTable') = 1)

drop table [dbo].[Tbl]

GO

 

CREATE TABLE [dbo].[Tbl] (

[X] [int] NULL,

[Y] [int] NULL,

[value] [varchar] (50))

GO

 

--- Заполнение тестовыми данными

insert into Tbl(X, Y, Value) VALUES (1, 10, 'Алма-Ата')

insert into Tbl(X, Y, Value) VALUES (2, 9, 'Алушта')

insert into Tbl(X, Y, Value) VALUES (3, 8, 'Алупка')

insert into Tbl(X, Y, Value) VALUES (4, 7, 'Анкара')

insert into Tbl(X, Y, Value) VALUES (5, 6, 'Агра')

insert into Tbl(X, Y, Value) VALUES (6, 5, 'Анапа')

insert into Tbl(X, Y, Value) VALUES (7, 4, 'Альбукерке')

insert into Tbl(X, Y, Value) VALUES (8, 3, 'Алансон')

insert into Tbl(X, Y, Value) VALUES (9, 2, 'Авиньен')

insert into Tbl(X, Y, Value) VALUES (10, 1, 'Абакан')Первый пример

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

Первая транзакция - T1.

BEGIN TRAN

UPDATE Tbl SET X = 1 WHERE X = 1

UPDATE Tbl SET X = 3 WHERE X = 3

COMMIT TRANВторая транзакция - T2.

BEGIN TRAN

UPDATE Tbl SET X = 3 WHERE X = 3

UPDATE Tbl SET X = 1 WHERE X = 1

COMMIT TRANЕсли эти транзакции стартуют одновременно, то произойдет взаимоблокировка по причине очевидного нарушения порядка доступа. T1 сначала обращается к записи X = 1, а затем к записи X = 3. Т2 же, наоборот, сначала обращается к записи X = 3, а затем к X = 1.

Рисунок 1. Порядок обращения к записям, приводящий к взаимоблокировке.

При одновременном старте Т1 захватывает запись X = 1, в это время Т2 успевает захватить запись X = 3. Затем T1 хочет захватить запись X = 3, но она уже захвачена T2, поэтому T1 ожидает T2 на блокировке, и в граф добавляется ребро T1->T2. Примерно в это же время T2 хочет захватить запись X = 1, которая также уже захвачена T1. В графе ожидания появляется второе ребро T2->T1 и он становится цикличным. Ну а поскольку подобная ситуация без грубого вмешательства неразрешима, то одна из транзакций будет отменена, другая же, пользуясь тем, что блокировка исчезла, спокойно завершит свою работу.

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

Второй пример

Следующие примеры я постараюсь разобрать более подробно, эмулируя поведение сервера в реальной ситуации.

Очень часто встречается примерно такая последовательность операторов в одной транзакции:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

BEGIN TRAN

SELECT @Var = Y FROM Tbl WHERE X = 2

---

--- здесь выполняются какие-нибудь вычисления над @Var.

---

UPDATE Tbl SET Y = @Var WHERE X = 2

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

Выполним вышеприведенный T-SQL-код из транзакций T1 и T2. Чтобы имитировать возможное развитие событий при параллельной работе будем выполнять транзакции по частям. Сначала половину T1, затем целиком T2, а потом оставшуюся часть T1. Эффект будет точно таким же, как если бы в реальном приложении между двумя операторами T1 успела бы пролезть транзакция T2. На самом деле для получения взаимоблокировки достаточно, чтобы между двумя операторами T1 успел втиснуться только первый оператор T2, дальнейший порядок операций уже не важен.

Итак, выполним первую часть T1 в одном из окон Query Analyserа:

--- установим необходимый уровень изоляции

SET ISOLATION LEVEL REPEATABLE READ

BEGIN TRANSACTION

SELECT * FROM Tbl WHERE X = 2Все выполнилось успешно, но транзакция все еще считается активной, мы ее не отменили и не зафиксировали. Если в этот момент посмотреть на блокировки, наложенные на таблицу Tbl, можно увидеть примерно следующую картину, с точностью до констант:

spid dbid ObjId ObjName IndId Type Resource Mode Status

------ ------ ---------- ------- ----- ---- --------- ----- ------

54 6 2034106287 Tbl 0 PAG 1:17495 IS GRANT

54 6 2034106287 Tbl 0 RID 1:17495:1 S GRANT

54 6 2034106287 Tbl 0 TAB IS GRANTИными словами, мы наложили коллективную блокировку (S) на конкретную запись (RID 1:17495:1), и две коллективные блокировки намерения (IS) выше по иерархии, на страницу и таблицу. Откроем новое соединение с той же базой в новом окне QA и попытаемся выполнить эту же транзакцию целиком:

--- установим необходимый уровень изоляции

SET ISOLATION LEVEL REPEATABLE READ

BEGIN TRAN

SELECT * FROM Tbl WHERE X = 2

UPDATE Tbl SET Y = 3 WHERE X = 2

COMMIT TRANБлокировок, естественно, добавилось:

spid dbid ObjId ObjName IndId Type Resource Mode Status

------ ------ ----------- ------- ------ ---- ---------- ----- -------

54 6 2034106287 Tbl 0 PAG 1:17495 IS GRANT

54 6 2034106287 Tbl 0 RID 1:17495:1 S GRANT

54

Похожие работы

<< < 1 2 3 4 5 6 7 > >>