Хорошие причины использовать SELECT… С XLOCK?

11

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

Посмотреть:

create view dbo.viewE
 as
    select * from dbo.E  
    where myValue > 13000 

Выберите Запрос:

select * from dbo.viewE with (XLOCK) where A > GETUTCDATE() 

ВСТАВИТЬ Заявление:

INSERT INTO [dbo].[E] (myValue,A) VALUES (10,GetDate())

Базовая таблица dbo.E содержит около 3 миллионов строк в примерно 20 столбцах, некоторые из них являются текстовыми.

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

График тупика:

<deadlock-list>
 <deadlock victim="process222222221">
  <process-list>
   <process id="process222222221" taskpriority="0" logused="0" waitresource="KEY: 5:72057604035644444 (ccdf51accc0c)" waittime="2522" ownerId="27202256401" transactionname="SELECT" lasttranstarted="2015-09-14T16:32:36.160" XDES="0x2f1ec5ca0" lockMode="RangeX-X" schedulerid="15" kpid="12936" status="suspended" spid="359" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2015-09-14T16:32:36.160" lastbatchcompleted="2015-09-14T16:32:36.160" clientapp="x" hostname="x" hostpid="14536" loginname="x" isolationlevel="serializable (4)" xactid="27202256401" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="48" sqlhandle="0x02000000611e4523142b2318c47c87313a9b2ba587ff3130">
        SELECT * FROM viewE WITH (XLOCK) WHERE A &lt; GetUtcDate()      </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
(@UICulture nvarchar(5))SELECT * FROM viewE WITH (XLOCK) WHERE A &lt; GetUtcDate()    </inputbuf>
   </process>
   <process id="process6022222" taskpriority="0" logused="161152" waitresource="KEY: 5:72057604035644444 (cd874c2ba438)" waittime="1370" ownerId="27202248438" transactionguid="0x8de5ccd6eeef67469c6234af59e44ca5" transactionname="DTCXact" lasttranstarted="2015-09-14T16:32:34.767" XDES="0x4aa0bf950" lockMode="RangeI-N" schedulerid="14" kpid="6636" status="suspended" spid="329" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-09-14T16:32:37.300" lastbatchcompleted="2015-09-14T16:32:37.300" clientapp="x" hostname="x" hostpid="14536" loginname="x" isolationlevel="read uncommitted (1)" xactid="27202248438" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="936" sqlhandle="0x020000004853462f09790a4ddedc0d574c2afa539aef1c0e">
     INSERT INTO [E] ([a], [b], [c],...) VALUES (@aDate, @bDate, @c, ...)
     </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>INSERT INTO [E] ([a], [b], [c],...) VALUES (@aDate, @bDate, @c, ...)
    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <keylock hobtid="72057604035644444" dbid="5" objectname="db.dbo.E" indexname="IX_index1" id="lock258b6dc80" mode="X" associatedObjectId="72057604035644444">
    <owner-list>
     <owner id="process6022222" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process222222221" mode="RangeX-X" requestType="wait"/>
    </waiter-list>
   </keylock>
   <keylock hobtid="72057604035644444" dbid="5" objectname="db.dbo.E" indexname="IX_index1" id="lock7b145c400" mode="RangeX-X" associatedObjectId="72057604035644444">
    <owner-list>
     <owner id="process222222221" mode="RangeX-X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process6022222" mode="RangeI-N" requestType="wait"/>
    </waiter-list>
   </keylock>
  </resource-list>
 </deadlock>
</deadlock-list>

Насколько я понимаю, я смотрю на тупик KEYLOCK, в основном вызванный запросом с непокрытым индексом, который использует некластеризованный и кластеризованный индекс для сбора необходимых значений, верно?

Мои вопросы:

  1. Я не могу создать индекс покрытия из-за необходимых столбцов NTEXT. Поможет ли здесь радикальное сокращение количества рядов?
  2. Есть ли веская причина, по которой я просто не знаю, что SELECT выполняется с помощью XLOCK? Будет ли тупик также без XLOCK?
Magier
источник

Ответы:

15

Насколько я понимаю, я смотрю на тупик KEYLOCK, в основном вызванный запросом с непокрытым индексом, который использует некластеризованный и кластеризованный индекс для сбора необходимых значений, верно?

По сути, да. Операция чтения (выбор) сначала обращается к некластеризованному индексу, затем к кластерному индексу (поиск). Операция записи (вставка) сначала обращается к кластерному индексу, а затем к некластерному индексу. Доступ к одним и тем же ресурсам в другом порядке с несовместимыми блокировками может привести к тупику.

Поможет ли здесь радикальное сокращение количества рядов?

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

Есть ли веская причина, по которой я просто не знаю, что SELECT выполняется с помощью XLOCK?

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

Будет ли тупик также без XLOCK?

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

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

Совет

Что действительно выделяется (на рассмотрении), так это то, что транзакция select выполняется в сериализуемой изоляции . Это может быть установлено вашей платформой или из-за использования DTC (координатора распределенных транзакций) - см. Транзакция name = "DTCXact" на графике взаимоблокировок. Вы должны изучить причины этого и попытаться изменить его, если это возможно.

Без этой эскалации до сериализуемой вероятности очень высоки, что этот тупик не возникнет, предполагая, что XLOCKподсказка удалена. Тем не менее, вы будете читать в режиме чтения без фиксации , что дает очень мало гарантий согласованности.

Если ваше приложение и код SQL Server могут допускать чтение версий строк, переключение на чтение зафиксированных моментальных снимков (RCSI) или изоляцию моментальных снимков (SI) для чтения также позволит избежать тупиковой ситуации ( XLOCKудалено!) При одновременном представлении согласованной точки время просмотра зафиксированных данных. Это также предполагает, что вы можете избежать сериализуемой изоляции, конечно.

В конце концов, XLOCKнамек является непродуктивным, но вы действительно должны смотреть на причины для использования сериализуемого уровня изоляции. Это trancount = 2также интересно - возможно, вы непреднамеренно вкладываете сюда транзакции. Что-то еще, чтобы проверить.

Пол Уайт 9
источник
2
  1. Резкое сокращение числа строк уменьшит вероятность возникновения тупика, но он не исчезнет полностью.

Проще говоря, выбор сначала использует индекс, чтобы определить строки для выбора, затем выбирает строки, в то время как вставка вставляет строку, а затем пытается обновить индекс (XLOCKED).

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

Сказав это, удаление XLOCK, вероятно, не решит проблему. SELECT будет по-прежнему снимать разделяемую блокировку индекса, а INSERT потребует, чтобы XLOCK обновил его. Совместная блокировка и XLOCK не могут существовать на объекте вместе, поэтому вы все равно получите тупик. IX_Index1 должен быть либо MyValue, либо A, либо обоими.

Этот тип взаимоблокировки часто возникает из-за плохо разработанных индексов и / или слишком большого количества индексов. Или плохо написанный код. Лучший вариант - посмотреть, можно ли переписать выбор для использования другого индекса.

Лео Миллер
источник