SQL Server тупик на той же таблице - PullRequest
9 голосов
/ 02 декабря 2011

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

Теперь, когда я читаю в журнале ошибок информацию о взаимоблокировках, которые я могу 'Я не понимаю, как эта ситуация может существовать.Посмотрите на это (я переименовал имена таблиц, но важным является имя с именем 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

Способ, которым я читаю это:

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

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

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

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

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

Ответы [ 3 ]

3 голосов
/ 05 декабря 2011

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

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

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

Эта статья говорит, среди прочего:

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

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

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

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

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

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

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

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

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

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

2 голосов
/ 02 декабря 2011

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

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

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

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

0 голосов
/ 02 декабря 2011

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...