Взаимоблокировка SQL Server в той же таблице

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

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

deadlock-list
deadlock victim=process1e2ac02c8
process-list
    process id=process1e2ac02c8 taskpriority=0 logused=0 waitresource=OBJECT: 11:290100074:0  waittime=704 ownerId=3144354890 transactionname=SELECT lasttranstarted=2011-12-01T14:43:20.577 XDES=0x80017920 lockMode=S schedulerid=6 kpid=7508 status=suspended spid=155 sbid=0 ecid=0 priority=0 trancount=0 lastbatchstarted=2011-12-01T14:43:20.577 lastbatchcompleted=2011-12-01T14:43:20.577 clientapp=.Net SqlClient Data Provider hostname=DE-1809 hostpid=5856 loginname=2Ezy isolationlevel=read committed (2) xactid=3144354890 currentdb=11 lockTimeout=4294967295 clientoption1=673185824 clientoption2=128056
     executionStack
      frame procname=.dbo.RetrieveSomething line=23 stmtstart=1398 stmtend=3724 sqlhandle=0x03000b0030d42d645a63e6006a9f00000100000000000000
         select
            Col1
            ,Col2
            ,(
                SELECT TOP(1)
                    Col1
                FROM
                    OurTable2 AS C
                        JOIN OurTable AS ETC ON C.Id = ETC.FKId
                            AND E.Id = C.FKId
                ORDER BY ETC.Col2
            ) AS Col3
        from OurTable3 AS E
    process id=process2df4894c8 taskpriority=0 logused=0 waitresource=OBJECT: 11:290100074:0  waittime=9713 ownerId=3144330250 transactionname=INSERT EXEC lasttranstarted=2011-12-01T14:43:11.573 XDES=0x370764930 lockMode=S schedulerid=13 kpid=4408 status=suspended spid=153 sbid=0 ecid=0 priority=0 trancount=1 lastbatchstarted=2011-12-01T14:43:11.573 lastbatchcompleted=2011-12-01T14:43:11.573 clientapp=.Net SqlClient Data Provider hostname=DE-1809 hostpid=5856 loginname=2Ezy isolationlevel=read committed (2) xactid=3144330250 currentdb=11 lockTimeout=4294967295 clientoption1=673185824 clientoption2=128056
     executionStack
      frame procname=adhoc line=1 sqlhandle=0x02000000ba6cb42612240bdb19f7303e279a714276c04344
         select
            Col1
            , Col2
            , Col3
            , ISNULL(
                (select top(1)
                    E_SUB.Col1 + ' ' + E_SUB.Col2
                    from OurTable3 as E_SUB 
                        inner join OurTable2 as C on E_SUB.Id = C.FKId
                        inner join OurTable as ETC on C.Id = ETC.FKId
                as Col3
        from OurTable4
            inner join dbo.OurTable as ETC on Id = ETC.FKId  
    process id=process8674c8 taskpriority=0 logused=0 waitresource=OBJECT: 11:290100074:5  waittime=338 ownerId=3143936820 transactionname=INSERT lasttranstarted=2011-12-01T14:38:24.423 XDES=0x1ecd229f0 lockMode=X schedulerid=7 kpid=12092 status=suspended spid=124 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2011-12-01T14:38:23.027 lastbatchcompleted=2011-12-01T14:38:23.013 clientapp=.Net SqlClient Data Provider hostname=DE-1809 hostpid=5856 loginname=2Ezy isolationlevel=read committed (2) xactid=3143936820 currentdb=11 lockTimeout=4294967295 clientoption1=673185824 clientoption2=128056
     executionStack
      frame procname=.dbo.UpsertSomething line=332 stmtstart=27712 stmtend=31692 sqlhandle=0x03000b00bbf2a93c0f63a700759f00000100000000000000
            insert into dbo.OurTable
            (
                Col1
                ,Col2
                ,Col3
            )
            values
            (
                @Col1
                ,@Col2
                ,@Col3
            )
       resource-list
        objectlock lockPartition=0 objid=290100074 subresource=FULL dbid=11 objectname=dbo.OurTable id=lock16a1fde80 mode=X associatedObjectId=290100074
         owner-list
         waiter-list
          waiter id=process1e2ac02c8 mode=S requestType=wait
        objectlock lockPartition=0 objid=290100074 subresource=FULL dbid=11 objectname=dbo.OurTable id=lock16a1fde80 mode=X associatedObjectId=290100074
         owner-list
          owner id=process8674c8 mode=X
         waiter-list
          waiter id=process2df4894c8 mode=S requestType=wait
        objectlock lockPartition=5 objid=290100074 subresource=FULL dbid=11 objectname=dbo.OurTable id=lock212f0f300 mode=IS associatedObjectId=290100074
         owner-list
          owner id=process1e2ac02c8 mode=IS
         waiter-list
          waiter id=process8674c8 mode=X requestType=wait

путь I прочтите это:

spid 155 ждет блокировки общей таблицы на OurTable (spid 124 содержит противоречивую блокировку X)

spid 153 ждет блокировки общей таблицы на OurTable (spid 124 содержит противоречивую блокировку X)

spid 124 ждет эксклюзивной блокировки таблицы на OurTable (spid 155 содержит конфликтующий is lock)

мой вопрос как это может произойти. Два сеанса удерживают одну блокировку на всей таблице одновременно. Я думал, что обычный тупик-это когда два или более сеанса держат блокировки на разных ресурсах и ждут друг друга. Но здесь замок находится на том же ресурсе. Это блокировка не индекса, а таблицы. Эта ошибка часто встречается в нашем приложении, и какая-то блокировка должна быть первой, которую нужно запросить, и почему принимается вторая блокировка, если на всей таблице уже есть блокировка?

любой, кто может дать намек на то, что может быть неправильно, или кто-то испытал подобный тупик?

3 ответов


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

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

Как Мартин написал в своем комментарии, ресурсы ожидания: 11:290100074:0 и 11:290100074: 5. После поиска оказывается, что при запуске Sql Server R2 на машине с 16 процессорами или более Sql Server может использовать функция называется секционирование.

в этой статье говорится, между прочим:

только режимы блокировки NL, SCH-S, IS, IU и IX приобретаются на одном раздел.

Что происходит в моем случае, так это то, что spid 155 помещает общую блокировку на строку или страницу, и поэтому помещает предполагаемую общую блокировку на объект, и с функцией блокировки раздела это происходит на идентификаторе раздела 5.

в то же время spid 124 должен для блокировки полного объекта с эксклюзивной блокировкой и для этого необходимо поставить X lock на все разделы.

Shared (S), exclusive (X) и другие блокировки в режимах, отличных от NL, SCH-S, IS, IU и IX должны быть приобретены на всех разделах, начиная с идентификатор секции 0 и далее в ID раздела порядка.

когда он прибывает в раздел id 5, ему говорят, что spid 155 содержит блокировку IS, и ему нужно подождать, пока эта блокировка не будет освобождена.

теперь когда spid 124 ждет разблокировки IS lock эскалация блокировки происходит на spid 155, и он запрашивает общую блокировку на таблице. Это означает, что он должен поместить блокировку S на все разделы, начиная с id 0. Но сразу же на id 0 он попадает в стену, потому что spid 124 уже имеет эксклюзивный замок на этом разделе. И в этом причина тупика.

Я не могу гарантировать 100%, это точный ответ, но я уверен, что я, если не на 100% прав, по крайней мере близко к ответу.

решение? Что ж. Функция блокировки раздела не может быть отключена, но, с другой стороны, вы можете управлять эскалация блокировки с различными уровнями транзакций, а также различными параметрами в инструкции alter table.

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

надеюсь, что этот ответ поможет другим с аналогичными проблемами.


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

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

еще одна вещь: ответ на этот комментарий" ни один из запросов не выполняется в транзакции " - каждый оператор DML всегда выполняется в транзакции, и ДМЛ тоже выбирает. Все команды, участвующие в взаимоблокировке, выполняются в контексте транзакции. Перейдите по второй ссылке и запустите скрипты repro-сами увидите.

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


это происходит потому, что ваша стратегия блокировки является упрощенной, и вы сталкиваетесь с случаем, когда это приходит и вредит вам.

Как в: если вы выбираете с достаточно плохим уровнем блокировки, вы получаете блокировку чтения и не можете обновить блокировку записи, пока другая блокировка чтения находится на месте, если это делают два приложения... у вас может быть точно поведение thagt (процесс 1 получает чтение ock, 2 получает блокировку чтения, 1 хочет обновить для записи блокировки (ждет), 2 хочет обновить для записи блокировки - взаимоблокировки.

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

эта ошибка часто встречается в нашем приложении, и какой-то замок должен быть первым, который будет запрошен и почему принимается второй замок, если на всей таблице уже есть замок?

вопрос дизайн для начинающих. Проблема возникает, потому что некоторые блокировки являются общими (в основном блокировки чтения), позволяя другим блокировкам чтения быть etablished. Если вы позволите. Я бы предложил убедиться, что тупик не произойдет. Либо чтение не оставляет блокировки (с NOLOCK), либо получает правильные блокировки записи намного раньше.