Я решаю проблему взаимоблокировки, хотя я заметил, что поведение блокировки отличается, когда я использую кластерный и некластеризованный индекс в поле id. Проблема взаимоблокировки, кажется, решена, если кластерный индекс или первичный ключ применен к полю id.
У меня есть разные транзакции, делающие одно или несколько обновлений для разных строк, например, транзакция A будет обновлять только строки с ID = a, tx B будет касаться только строки с ID = b и т. Д.
И я понимаю, что без индекса обновление получит блокировку обновления для всех строк и при необходимости перейдет в монопольную блокировку, что в конечном итоге приведет к взаимоблокировке. Но я не могу понять, почему с некластеризованным индексом тупик все еще существует (хотя скорость попадания, кажется, снижается)
Таблица данных:
CREATE TABLE [dbo].[user](
[id] [int] IDENTITY(1,1) NOT NULL,
[userName] [nvarchar](255) NULL,
[name] [nvarchar](255) NULL,
[phone] [nvarchar](255) NULL,
[password] [nvarchar](255) NULL,
[ip] [nvarchar](30) NULL,
[email] [nvarchar](255) NULL,
[pubDate] [datetime] NULL,
[todoOrder] [text] NULL
)
След тупика
deadlock-list
deadlock victim=process4152ca8
process-list
process id=process4152ca8 taskpriority=0 logused=0 waitresource=RID: 5:1:388:29 waittime=3308 ownerId=252354 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.947 XDES=0xb0bf180 lockMode=U schedulerid=3 kpid=11392 status=suspended spid=57 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.953 lastbatchcompleted=2014-04-11T00:15:30.950 lastattention=1900-01-01T00:00:00.950 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252354 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=62 sqlhandle=0x0200000062f45209ccf17a0e76c2389eb409d7d970b0f89e00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(2)<c/>@owner int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
process id=process4153468 taskpriority=0 logused=4652 waitresource=KEY: 5:72057594042187776 (3fc56173665b) waittime=3303 ownerId=252344 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.920 XDES=0x4184b78 lockMode=U schedulerid=3 kpid=7272 status=suspended spid=58 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.960 lastbatchcompleted=2014-04-11T00:15:30.960 lastattention=1900-01-01T00:00:00.960 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252344 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=60 sqlhandle=0x02000000d4616f250747930a4cd34716b610a8113cb92fbc00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(61)<c/>@uid int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
resource-list
ridlock fileid=1 pageid=388 dbid=5 objectname=SQL2012_707688_webows.dbo.user id=lock3f7af780 mode=X associatedObjectId=72057594042122240
owner-list
owner id=process4153468 mode=X
waiter-list
waiter id=process4152ca8 mode=U requestType=wait
keylock hobtid=72057594042187776 dbid=5 objectname=SQL2012_707688_webows.dbo.user indexname=10 id=lock3f7ad700 mode=U associatedObjectId=72057594042187776
owner-list
owner id=process4152ca8 mode=U
waiter-list
waiter id=process4153468 mode=U requestType=wait
Также интересным и возможным связанным с этим выводом является то, что кластерный и некластеризованный индекс, по-видимому, имеют различные поведения блокировки
При использовании кластеризованного индекса существует исключительная блокировка ключа, а также исключительная блокировка RID при обновлении, что ожидается; в то время как есть две монопольных блокировки на два разных RID, если используется некластеризованный индекс, что меня смущает.
Было бы полезно, если кто-нибудь может объяснить, почему на этом тоже.
Тест SQL:
use SQL2012_707688_webows;
begin transaction;
update [user] with (rowlock) set todoOrder='{1}' where id = 63501
exec sp_lock;
commit;
С идентификатором Clustered Index:
spid dbid ObjId IndId Type Resource Mode Status
53 5 917578307 1 KEY (b1a92fe5eed4) X GRANT
53 5 917578307 1 PAG 1:879 IX GRANT
53 5 917578307 1 PAG 1:1928 IX GRANT
53 5 917578307 1 RID 1:879:7 X GRANT
С идентификатором как некластеризованный индекс
spid dbid ObjId IndId Type Resource Mode Status
53 5 917578307 0 PAG 1:879 IX GRANT
53 5 917578307 0 PAG 1:1928 IX GRANT
53 5 917578307 0 RID 1:879:7 X GRANT
53 5 917578307 0 RID 1:1928:18 X GRANT
EDIT1: детали тупиковой ситуации без индекса.
Скажем, у меня есть два tx A и B, каждый с двумя операторами обновления, разные строки, конечно,
tx A
update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501
TX B
update [user] with (rowlock) set todoOrder='{3}' where id = 63502
update [user] with (rowlock) set todoOrder='{4}' where id = 63502
{1} и {4} могут оказаться в тупике, поскольку
в {1} блокировка U запрашивается для строки 63502, поскольку ей необходимо выполнить сканирование таблицы, и блокировка X могла быть удержана в строке 63501, поскольку она соответствует условию
в {4} запрашивается блокировка U для строки 63501, а блокировка X уже удерживается для 63502
таким образом, у нас есть txA, удерживающий 63501 и ожидающий 63502, в то время как txB держит 63502, ожидая 63501, что является тупиком
РЕДАКТИРОВАТЬ 2: Оказывается, ошибка в моем тестовом примере имеет значение ситуации Извините за путаницу, но ошибка создает ситуацию и, похоже, в конечном итоге вызывает тупик.
Поскольку анализ Пола действительно помог мне в этом деле, поэтому я приму это как ответ.
Из-за ошибки в моем тестовом примере две транзакции txA и txB могут обновить одну и ту же строку, как показано ниже:
TX A
update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501
TX B
update [user] with (rowlock) set todoOrder='{3}' where id = 63501
{2} и {3} могут зайти в тупик, когда:
txA запрашивает U-блокировку на ключе, в то время как удерживает X-блокировку на RID (из-за обновления {1}) txB запрашивает U-блокировку на RID, пока удерживает U-блокировку на ключе
источник
Ответы:
Вопрос не совсем ясен (например, сколько обновлений и к каким
id
значениям в каждой транзакции), но возникает один очевидный сценарий взаимоблокировки с несколькими однострочными обновлениями в одной транзакции, где есть перекрытие[id]
значений, и идентификаторы обновляется в другом[id]
порядке:Последовательность взаимоблокировки: Т1 (и2), Т2 (u1), Т1 (u1) ожидание , Т2 (и2) ожидание .
Эту последовательность взаимоблокировок можно избежать, если выполнять строгое обновление идентификатора в каждой транзакции (получение блокировок в том же порядке на том же пути).
При включенном уникальном кластеризованном индексе для
id
ключа кластеризации устанавливается исключительная блокировка для защиты записей в данных в строке. ОтдельнаяRID
монопольная блокировка требуется для защиты записи вtext
столбец больших объектов , который по умолчанию хранится на отдельной странице данных.Когда таблица представляет собой кучу с включенным только некластеризованным индексом
id
, происходят две вещи. Во-первых, однаRID
исключительная блокировка относится к данным в куче, а другая - это блокировка данных больших объектов, как и раньше. Второй эффект заключается в том, что требуется более сложный план выполнения.С помощью кластеризованного индекса и простого обновления предиката равенства с одним значением процессор запросов может применить оптимизацию, которая выполняет обновление (чтение и запись) в одном операторе, используя один путь:
Строка находится и обновляется в одной операции поиска, требующей только исключительных блокировок (блоки обновлений не требуются). Пример последовательности блокировки с использованием вашей таблицы образцов:
Только с некластеризованным индексом такая же оптимизация не может быть применена, потому что нам нужно читать из одной структуры b-дерева и писать другую. Многопутевой план имеет отдельные фазы чтения и записи:
Это получает обновления блокировки при чтении, преобразовывая в эксклюзивные блокировки, если строка соответствует. Пример последовательности блокировки со схемой:
Обратите внимание, что данные больших объектов читаются и записываются в итераторе обновления таблицы. Более сложный план и несколько путей чтения и записи увеличивают вероятность тупиковой ситуации.
Наконец, я не могу не заметить типы данных, используемые в определении таблицы. Вы не должны использовать устаревший
text
тип данных для новой работы; альтернативой, если вам действительно требуется возможность хранить до 2 ГБ данных в этом столбце, являетсяvarchar(max)
. Одним из важных отличий междуtext
иvarchar(max)
является то, чтоtext
по умолчанию данные хранятся вне строки, а по умолчанию -varchar(max)
внутри строки.Используйте типы Unicode только в том случае, если вам нужна такая гибкость (например, трудно понять, зачем IP-адресу нужен Unicode). Кроме того, выберите соответствующие ограничения длины для ваших атрибутов - 255 везде, кажется, не будет правильным.
Дополнительные материалы:
общие схемы
взаимоблокировок и взаимоблокировок Bart Duncan.
Отслеживание замков может быть выполнено различными способами. SQL Server Express с расширенными службами (только 2014 и 2012 SP1 ) содержит инструмент Profiler , который является поддерживаемым способом просмотра сведений о получении и снятии блокировки.
источник