Тупик между удалением и обновлением

Первый процесс:

<deadlock-list>
 <deadlock victim="process8d9798">
  <process-list>
   <process id="process8d9798" taskpriority="0" logused="0" waitresource="PAGE:
    5:1:190354" waittime="3203" ownerId="53807810" transactionname="DELETE" 
    lasttranstarted="11:29:29.153" XDES="0x3dbb518" lockMode="U" 
    schedulerid="2" kpid="1792" status="suspended" spid="57" sbid="0" ecid="1" 
    priority="0" transcount="0" lastbatchstarted="2012-09-28T11:29:29.120" 
    lastbatchcompleted="11:29:29.120" clientapp=".Net SqlClient Data Provider" 
    hostname="xxx" hostpid="4460" isolationlevel="read uncommitted (1)" 
    xactid="53807810" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" 
    clientoption2="128056">
    <executionStack>
     <frame procname="Chooser2.dbo.DeleteUserSelections" line="15" stmtstart="360"
     stmtend="464" sqlhandle="0x030005008839117bf599a500099800000100000000000000">
   DELETE UserPlanOption
    WHERE UserID = @userId     </frame>
    </executionStack>
    <inputbuf>
    </inputbuf>
   </process>

Второй процесс:

   <process id="processb84988" taskpriority="0" logused="1744" waitresource="PAGE:
    5:1:190487" waittime="3203" ownerId="53807415" transactionname="user_transaction" 
   lasttranstarted="11:29:13.513" XDES="0x2fc4e6e0" lockMode="IU" 
   schedulerid="4" kpid="4628" status="suspended" spid="52" sbid="0" ecid="0" 
   priority="0" transcount="2" lastbatchstarted="11:29:13.513" 
   lastbatchcompleted="11:29:13.513" 
   clientapp=".Net SqlClient Data Provider" hostname="xxx" 
   hostpid="4460" loginname="chooserpd" isolationlevel="read uncommitted (1)" 
   xactid="53807415" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" 
   clientoption2="128056">
    <executionStack>
     <frame procname="Eligibility" line="149" stmtstart="10566" 
   stmtend="11604" sqlhandle="0x03000500171b4f52c1a6e200ada000000100000000000000">
UPDATE  UserPlanOption 
    SET     RateID = r.ID
    FROM    [User] u WITH (NOLOCK)
        LEFT JOIN Rate r ON r.FamilyTierID = u.FamilyTierID 
    WHERE   UserPlanOption.PlanOptionID NOT IN (SELECT ppo.PlanOptionID FROM 
                    @PORACPlanOptions ppo) AND
        u.ID = @userID AND u.ID = UserPlanOption.UserID AND
        r.PlanOptionID = UserPlanOption.PlanOptionID AND
        r.Criterion1 = dbo.GetPlanOptionAreaID_36()
      </frame>
    </executionStack>
    <inputbuf>
Proc [Database Id = 5 Object Id = 1380915991]    </inputbuf>
   </process>
  </process-list>

Список ресурсов:

  <resource-list>
   <pagelock fileid="1" pageid="190354" dbid="5" objectname="UserPlanOption" 
   id="lock1e482d80" mode="IX" associatedObjectId="72057594060996608">
    <owner-list>
     <owner id="processb84988" mode="IX"/>
    </owner-list>
    <waiter-list>
     <waiter id="process8d9798" mode="U" requestType="wait"/>
    </waiter-list>
   </pagelock>
   <pagelock fileid="1" pageid="190487" dbid="5" objectname="UserPlanOption" 
   id="lock25b32a80" mode="U" associatedObjectId="72057594060996608">
    <owner-list>
     <owner id="process8d9798" mode="U"/>
    </owner-list>
    <waiter-list>
     <waiter id="processb84988" mode="IU" requestType="wait"/>
    </waiter-list>
   </pagelock>
  </resource-list>
 </deadlock>
</deadlock-list>

Таблица "UserPlanOption имеет составной PK (UserId и PlanOptionId). Почему удаление вызывает блокировку страницы? Может кто-нибудь помочь мне понять, что происходит? Некоторое время меня смутило то, что я думал, что тупик будет вызван последующим запросы от одного и того же клиента, но это невозможно. Это должны быть разные клиенты, обращающиеся к одной и той же веб-странице.

На самом деле, я думаю, я знаю ответ на первый вопрос - удаление диапазона потребует блокировки страницы. Но как я могу обойти это?

Результаты запроса индекса:

name                type type_desc is_unique data_space_id ignore_dup_key 
------------------- ---- --------- --------- ------------- -------------- 
PK_UserPlanOption_1 1    CLUSTERED 1         1             0              


is_primary_key is_unique_constraint fill_factor is_padded is_disabled 
-------------- -------------------- ----------- --------- ----------- 
1              0                    0           0         0           

is_hypothetical allow_row_locks allow_page_locks
--------------- --------------- ----------------
0               1               1

Планы выполнения обновления и удаления.

2 ответа

Решение

Ответ обновлен

наблюдения

Эта диаграмма основана на графике тупиков XML:

Это показывает, что spid52 имеет блокировку IX для pageid=190354 (таблица UserPlanOption) и запрашивает блокировку IU для pageid=190487 (из той же таблицы UserPlanOption). Я думаю, что таблица UserPlanOption является таблицей кучи, что означает, что она не имеет кластеризованного индекса. Кроме того, это означает, что ваш ПК некластеризован. Если вы запустите этот запрос:

SELECT i.*
FROM sys.indexes i
WHERE i.object_id = OBJECT_ID('UserPlanOption')

вы получите список со всеми индексами из таблицы UserPlanOption (вы можете опубликовать этот список?). В этом случае, поскольку spid52 выполняет UPDATE, обе блокировки (IU и IX) показывают (с моей точки зрения) возможный оператор сканирования таблицы / индекса в плане выполнения UPDATE.

Но spid57 уже имеет блокировку U для того же ресурса (pageid=190487). Это же соединение (spid57) запрашивает еще одну блокировку U на другой странице (pageid=190354), но этот ресурс (страница) уже заблокирован spid52 (IX).

Потому что (см. Блокировка матрицы совместимости):

[i] существующий IX и запрошенные U-блокировки или

[ii] существующие U и запрошенные блокировки IU

не совместимы у вас хороший тупик.

Кэшированный план для оператора DELETE

Заметки:

  • Теперь оператор сканирования кластеризованного индекса (с параллелизмом) заставляет СУБД сканировать все строки из таблицы UserPlanOption,

  • Расчетное количество строк составляет всего 5 (расчетное количество удаленных строк), а наличие операторов * Parallelism показывает, что таблица UserPlanOption велика,

  • Вы можете увидеть предложение индекса от SQL Server.

Кэшированный план для оператора UPDATE

Основными проблемами этого плана являются: Сканирование кластеризованного индекса по таблице тарифов, Вычислительный скаляр с неявным преобразованием из [N][VAR]CHAR (?) в INT и фильтр перед JOIN с таблицей User.

Решения

На основании этих наблюдений решения должны быть:

[1] CREATE INDEX IN_UserPlanOption_UserID_PlanOptionID ON UserPlanOption (UserID, PlanOptionID);

-- SQL Server's suggestion
CREATE INDEX IN_UserPlanOption_UserID
ON UserPlanOption(UserID)
INCLUDE(PlanOptionID); -- optional

Примечание 1: по моему выбору, проверка кластеризованного индекса по таблице UserPlanOption (DELETE) является основной причиной этой тупиковой ситуации.

Примечание 2: UserPlanOption имеет кластерный индекс на (PlanOptionID, UserID) колонны. Этот индекс помогает оператору UPDATE (см. Оператор Seek в PK_UserPlanOption_1: WHERE ... AND u.ID = UserPlanOption.UserID AND r.PlanOptionID = UserPlanOption.PlanOptionID AND ...) но не оператор DELETE (WHERE UserID=@UserID).

[2] Чтобы повысить производительность оператора UPDATE, вы можете создать индекс sugested:

-- SQL Server's suggestion
CREATE INDEX IN_Rate_FamilyTierID
ON dbo.Rate(FamilyTierID)
INCLUDE (PlanOptionID, Criterion1);

[3] Чтобы удалить неявное преобразование, вы можете переписать оператор DELETE следующим образом:

DECLARE @Criterion1 Criterion1_datatype? 
SET @Criterion1 = dbo.GetPlanOptionAreaID_36()

UPDATE  UserPlanOption 
    SET     RateID = r.ID
    FROM    [User] u 
        LEFT JOIN Rate r ON r.FamilyTierID = u.FamilyTierID 
    WHERE   UserPlanOption.PlanOptionID NOT IN (SELECT ppo.PlanOptionID FROM 
                    @PORACPlanOptions ppo) AND
        u.ID = @userID AND u.ID = UserPlanOption.UserID AND
        r.PlanOptionID = UserPlanOption.PlanOptionID AND
        r.Criterion1 = @Criterion1

Исходный исходный код (инструкция UPDATE) включает этот фильтр r.Criterion = dbo.GetPlanOptionAreaID_36(...), В этот момент, если эта функция вызывается для каждой строки из таблицы показателей, оператор компьютерной скалярности может стать еще одной проблемой производительности.

Является ли эта функция детерминированной функцией?

SELECT  r.IS_DETERMINISTIC, r.*
FROM    INFORMATION_SCHEMA.ROUTINES r
WHERE   r.ROUTINE_NAME='GetPlanOptionAreaID_36'

[4] Мой совет не использовать NOLOCK подсказка и / или SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED, Это решение должно быть только последним решением.

Я полагаю, что причина тупика заключается в том, что к таблице UserPlanOption обращаются в другом порядке, скорее всего, из-за наличия индекса в столбце FamilyTierId.

Вы можете прочитать больше о подобных проблемах здесь и здесь

Одним из возможных способов избежать этого условия взаимоблокировки является предварительное получение блокировок на основе столбца UserID перед оператором UPdate, как в

SELECT @userId = UserId ИЗ UserPlanOption WITH(UPDLOCK) WHERE UserID = @userId

Другие вопросы по тегам