2016-10-03 15 views
2

Сегодня у нас был очень интересный случай, который меня очень озадачивает. В двух словах мы сделали несколько рефакторингов, очистив триггеры от повторяющегося кода, извлекая его в единую и многоразовую хранимую процедуру. Мы думали, что этот рефакторинг не будет иметь побочных эффектов, но мы были неправы. После выпуска мы столкнулись с множеством тупиков и ухудшением производительности без каких-либо очевидных причин. После проверки системных таблиц, чтобы увидеть, какая БД взята, мы выяснили, что рефакторинг выше был задействован, и мы закончили с откатом от обновления.Тупиковые замки после извлечения части триггера в хранимую процедуру

Мы не воспроизводили проблему в тестовых средах, чтобы исследовать лекарство, поэтому некоторые проблемы возникают, чтобы проблема была видимой.

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

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

CREATE TRIGGER [dbo].[TR__xyz__update_sync_publishers] 
ON [dbo].[xyz] 
AFTER INSERT, DELETE, UPDATE 
AS 
BEGIN 
    SET NOCOUNT ON; 

    if(TRIGGER_NESTLEVEL() = 1) 
    BEGIN 
     create table #AffectedIDs (advisor_id int primary key) 

     insert into #AffectedIDs 
      select distinct t.id 
      from 
       (select id 
       from inserted 
       inner join xyz a ON a.id = id 
       where [text] <> '' 
       union 
       select id 
       from inserted 
       inner join xyz a ON a.id = id 
       where [text] <> '') t 

     declare @date datetime = getutcdate() 
     declare @RegisteredObjectTypeID int = 2 
     declare @SyncPublisherSourceID int = 1 

     update pub 
     set pub.master_update_date = @date 
     from #AffectedIDs affected 
     inner join sync_publishers pub on 
      pub.sync_registered_object_type_id = @RegisteredObjectTypeID 
      and pub.sync_publisher_source_id = @SyncPublisherSourceID 
      and pub.sync_object_id = affected.advisor_id 

     insert into sync_publishers (sync_object_id, sync_registered_object_type_id, sync_publisher_source_id , master_update_date) 
     select 
      affected.advisor_id, 
      @RegisteredObjectTypeID, 
      @SyncPublisherSourceID, 
      @date 
     from #AffectedIDs affected 
     left join sync_publishers pub on 
      pub.sync_registered_object_type_id = @RegisteredObjectTypeID 
      and pub.sync_publisher_source_id = @SyncPublisherSourceID 
      and pub.sync_object_id = affected.advisor_id 
     where 
      pub.sync_object_id is null 

     drop table #AffectedIDs 
    END 
END 

Вот новый триггер, который блокирует.

CREATE TRIGGER [dbo].[TR__xyz__update_sync_publishers] 
    ON [dbo].[xyz] 
    AFTER INSERT,DELETE,UPDATE 
AS 
BEGIN 

    SET NOCOUNT ON; 

    declare @ids dtInt 

    insert into @ids 
    select distinct t.id 
    from 
    (
     select id from inserted 
     INNER JOIN xyz a ON a.id = id 
     WHERE [text] <> '' 
     union 
     select id from inserted 
     INNER JOIN xyz a ON a.id = id 
     WHERE [text] <> '' 
    ) t 

    exec SyncTracker_PublishEvent 2, @ids 

END 

Вот определение экстрагированной SP:

CREATE PROCEDURE [dbo].[SyncTracker_PublishEvent] 
    @objectTypeId int, 
    @ids dtInt readonly 
AS 
BEGIN 
    SET NOCOUNT ON; 

    if(TRIGGER_NESTLEVEL() > 1) RETURN; 

    declare @pubSourceId int = 1 
    declare @date datetime = getutcdate() 

    update pub 
    set pub.master_update_date = @date 
    from @ids affected 
    inner join sync_publishers pub 
    on pub.sync_registered_object_type_id = @objectTypeId 
     and pub.sync_publisher_source_id = @pubSourceId 
     and pub.sync_object_id = affected.value 

    insert into sync_publishers (sync_object_id, sync_registered_object_type_id, sync_publisher_source_id , master_update_date) 
    select affected.value, @objectTypeId, @pubSourceId, @date 
    from @ids affected 
    left join sync_publishers pub 
    on pub.sync_registered_object_type_id = @objectTypeId 
     and pub.sync_publisher_source_id = @pubSourceId 
     and pub.sync_object_id = affected.value 
    where 
     pub.sync_object_id is null 
END 
GO 

Определение dtInt.

CREATE TYPE [dbo].[dtInt] AS TABLE 
(
    [value] [int] NOT NULL, 
    PRIMARY KEY CLUSTERED 
    (
     [value] ASC 
    ) 
) 

И, наконец, график взаимоблокировки.

<deadlock> 
    <victim-list> 
    <victimProcess id="processe1892fe8c8" /> 
    </victim-list> 
    <process-list> 
    <process id="processe1892fe8c8" taskpriority="0" logused="3824" waitresource="KEY: 5:72057602924150784 (4776e78e2961)" waittime="5686" ownerId="2583257965" transactionname="user_transaction" lasttranstarted="2016-10-03T08:30:42.500" XDES="0xe192b24408" lockMode="U" schedulerid="6" kpid="41296" status="suspended" spid="141" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2016-10-03T08:30:42.503" lastbatchcompleted="2016-10-03T08:30:42.493" lastattention="2016-10-03T08:29:01.693" clientapp="..." hostname="..." hostpid="22572" loginname="kbuser" isolationlevel="read committed (2)" xactid="2583257965" currentdb="5" lockTimeout="4294967295" clientoption1="673316896" clientoption2="128056"> 
     <executionStack> 
     <frame procname="63c1b4d8-1c55-4429-b057-81fb6da8f780.dbo.SyncTracker_PublishEvent" line="21" stmtstart="1178" stmtend="1680" sqlhandle="0x030005007bf23c4b5012b40092a6000001000000000000000000000000000000000000000000000000000000"> 
update pub 
    set pub.master_update_date = @date 
    from @ids affected 
    inner join sync_publishers pub 
    on pub.sync_registered_object_type_id = @objectTypeId 
     and pub.sync_publisher_source_id = @pubSourceId 
     and pub.sync_object_id = affected.valu </frame> 
     <frame procname="63c1b4d8-1c55-4429-b057-81fb6da8f780.dbo.TR__xyz__update_sync_publishers" line="28" stmtstart="1300" stmtend="1372" sqlhandle="0x03000500f711233ddee4c60090a6000000000000000000000000000000000000000000000000000000000000"> 
exec SyncTracker_PublishEvent 2, @id </frame> 
     <frame procname="unknown" line="1" stmtstart="1054" stmtend="3032" sqlhandle="0x02000000912653235c5ef3529289f19ae4445e62ee1ccbc00000000000000000000000000000000000000000"> 
unknown </frame> 
     <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"> 
unknown </frame> 
     </executionStack> 
    </process> 
    <process id="processdfa401b848" taskpriority="0" logused="9384" waitresource="KEY: 5:72057602924150784 (1501093f83b4)" waittime="5814" ownerId="2582414029" transactionname="user_transaction" lasttranstarted="2016-10-03T08:30:09.933" XDES="0x104486ac408" lockMode="U" schedulerid="1" kpid="19548" status="suspended" spid="213" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2016-10-03T08:30:53.047" lastbatchcompleted="2016-10-03T08:30:53.047" lastattention="1900-01-01T00:00:00.047" clientapp="..." hostname="..." hostpid="6196" loginname="kbuser" isolationlevel="read committed (2)" xactid="2582414029" currentdb="5" lockTimeout="4294967295" clientoption1="673316896" clientoption2="128056"> 
     <executionStack> 
     <frame procname="63c1b4d8-1c55-4429-b057-81fb6da8f780.dbo.SyncTracker_PublishEvent" line="21" stmtstart="1178" stmtend="1680" sqlhandle="0x030005007bf23c4b5012b40092a6000001000000000000000000000000000000000000000000000000000000"> 
update pub 
    set pub.master_update_date = @date 
    from @ids affected 
    inner join sync_publishers pub 
    on pub.sync_registered_object_type_id = @objectTypeId 
     and pub.sync_publisher_source_id = @pubSourceId 
     and pub.sync_object_id = affected.valu </frame> 
     <frame procname="63c1b4d8-1c55-4429-b057-81fb6da8f780.dbo.TR__xyz__update_sync_publishers" line="28" stmtstart="1300" stmtend="1372" sqlhandle="0x03000500f711233ddee4c60090a6000000000000000000000000000000000000000000000000000000000000"> 
exec SyncTracker_PublishEvent 2, @id </frame> 
     <frame procname="unknown" line="1" stmtstart="1120" stmtend="3132" sqlhandle="0x020000007414d821ed68a2ab4462b4eca6b2fdb4ba28cc350000000000000000000000000000000000000000"> 
unknown </frame> 
     <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"> 
unknown </frame> 
     </executionStack> 
    </process> 
    </process-list> 
    <resource-list> 
    <keylock hobtid="72057602924150784" dbid="5" objectname="63c1b4d8-1c55-4429-b057-81fb6da8f780.dbo.sync_publishers" indexname="IX__sync_publishers__registered_object_type_id__sync_object_id" id="lock10887a96b00" mode="X" associatedObjectId="72057602924150784"> 
     <owner-list> 
     <owner id="processdfa401b848" mode="X" /> 
     </owner-list> 
     <waiter-list> 
     <waiter id="processe1892fe8c8" mode="U" requestType="wait" /> 
     </waiter-list> 
    </keylock> 
    <keylock hobtid="72057602924150784" dbid="5" objectname="63c1b4d8-1c55-4429-b057-81fb6da8f780.dbo.sync_publishers" indexname="IX__sync_publishers__registered_object_type_id__sync_object_id" id="lockdb7d7b8200" mode="X" associatedObjectId="72057602924150784"> 
     <owner-list> 
     <owner id="processe1892fe8c8" mode="X" /> 
     </owner-list> 
     <waiter-list> 
     <waiter id="processdfa401b848" mode="U" requestType="wait" /> 
     </waiter-list> 
    </keylock> 
    </resource-list> 
</deadlock> 

deadlock graph

Определение sync_publishers доступных здесь: http://pastebin.com/LviwwCDi.

Если у вас есть мысли о возможных причинах - пожалуйста, поделитесь им - мы будем очень признательны!

UPDATE 1. Фактические планы выполнения UPDATE/INSERT в sync_publishers

Фактические планы выполнения выглядит практически идентичны.

новый exec план (это иногда тупиковые блоки). new

Старый план exec (это не так). old

UPDATE 2. Пробовал некоторые советы

Я попытался несколько советов сегодня:

  1. Избавился «ключевых поисков» в планах запросов из-за отсутствия sync_publisher_source_id внутри не- кластеризованный индекс по столбцу удаления - это не было действительно обязательным в нашей реализации.

  2. Перепечатка UPDATE + INSERT как отдельная MERGE инструкция.

    MERGE sync_publishers2 t 
    USING @ids s 
    ON s.[value] = t.sync_object_id 
        and t.sync_registered_object_type_id = @objectTypeId 
    WHEN MATCHED 
        THEN UPDATE 
         SET master_update_date = @date 
    WHEN NOT MATCHED 
        THEN INSERT 
          (sync_object_id, sync_registered_object_type_id, master_update_date) 
         VALUES 
          (s.[value], @objectTypeId, @date); 
    

начали получать тупиков о MERGE отчетности. Новый график взаимоблокировки можно посмотреть здесь: http://pastebin.com/QNJk7tea.

UPDATE 3. Попытка MERGE намеки

Я пытался сделать MERGE с xlock и holdlock намеки - не повезло, хотя - опять попал в тупик на MERGE.

MERGE sync_publishers2 with(xlock, holdlock) t 
+0

Попробуйте проверить TRIGGER_NESTLEVEL() в теле триггера. В вашей версии proc этот код может выполняться дважды: 'insert into @ids выберите отличный t.id from()', который может сильно повлиять на время блокировки замков. – Serg

+0

Проверьте фактические планы выполнения инструкций «UPDATE pub» и «INSERT INTO sync_publishers» из триггера против SP. Вы получаете разные планы выполнения из-за оценки мощности? –

+0

@ RazvanSocol, спасибо за предложение! Я обновляю сообщение с фактическими планами выполнения для обоих случаев - выглядит почти идентично ... –

ответ

0

Вот версия, которая не кажется тупиковой после 1 часа в 3 сеанса рабочей нагрузки параллельно. Я не могу точно определить точную причину взаимоблокировок в первую очередь, но то, что я могу сделать, - это подчеркнуть разницу с попыткой взаимоблокировки, которая также охватывала утверждение MERGE: версия ниже (кажется, работает нормально) использует CTE для разрешения MERGE: ON выражение должно быть переписано таким образом, чтобы упоминать только столбец PK (sync_publisher_id).

CREATE PROCEDURE [dbo].[SyncTracker_PublishEvent2] 
    @objectTypeId int, 
    @ids dtInt readonly 
AS 
BEGIN 
    SET NOCOUNT ON; 

    -- stop recoursive propogations 
    if(TRIGGER_NESTLEVEL() > 1) RETURN; 

    declare @date datetime = getutcdate() 

    ;WITH sync_publishers2CTE AS 
    (
     SELECT [sync_publisher_id], 
       [sync_object_id], 
       [sync_registered_object_type_id], 
       [master_update_date] 
      FROM [dbo].[sync_publishers2] 
      WHERE sync_registered_object_type_id = @objectTypeId 
    ) 
    MERGE sync_publishers2CTE WITH (XLOCK) trg 
    USING 
    (
      SELECT sp.sync_publisher_id, 
        s.Value AS sync_object_id, 
        @objectTypeId AS sync_registered_object_type_id, 
        @date AS master_update_date 
       FROM @ids s 
       LEFT JOIN sync_publishers2 sp ON sp.sync_object_id = s.Value 
               AND sp.sync_registered_object_type_id = @objectTypeId 
    ) src 
    ON (trg.sync_publisher_id = src.sync_publisher_id) 
    WHEN MATCHED 
     THEN UPDATE 
      SET trg.master_update_date = src.master_update_date 
    WHEN NOT MATCHED 
     THEN INSERT 
       (sync_object_id, sync_registered_object_type_id, master_update_date) 
      VALUES 
       (sync_object_id, sync_registered_object_type_id, master_update_date); 
END 

План выполнения образца:

exec plan