проблемы взаимоблокировки с триггерами - PullRequest
2 голосов
/ 05 сентября 2011

Я получаю ошибки взаимоблокировки в своем живом приложении и отслеживаю их (используя "график взаимоблокировок" в профиле сервера SQL) до after insert триггеров, определенных в моих таблицах.

в основном сценарий такой: я хочу отслеживать записи, вставленные в определенные таблицы, сгруппированные по временным рамкам. (т.е. между 12: 00-12: 10, 7 записей были вставлены в Users).
Я реализовал способ создания after insert триггеров для этих таблиц, поэтому, когда запись будет вставлена, я обновлю соответствующую запись в моей таблице статистики. (увидеть ниже).

Как я уже сказал, это, кажется, создает тупиковые ситуации. Что происходит (я думаю , я не нашел способа быть уверенным в этом), так это то, что каждая транзакция может вставлять / обновлять несколько записей в нескольких таблицах до принятия.
Таким образом, происходит транзакция 1, обновляет определенную запись в таблице статистики (таким образом блокируя ее) и продолжает обновлять запись в таблице B.
Между тем, транзакция 2 вставляет запись в таблицу B (таким образом, блокируя ее) и пытается обновить запись в таблице статистики, что приводит к взаимоблокировке.

(это, конечно, очень упрощенная версия того, что может происходить. На самом деле я еще не уверен на 100%).

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

другим решением было бы полностью исключить триггеры и использовать вместо этого какое-то пакетное задание.

приветствуются любые другие идеи / мысли о предпочтительном решении.

код триггера:

SELECT  @TimeIn = I.TimeIn,
    @TimeOut = I.[TimeOut],
    FROM    Inserted AS I

        SET     @NoOfPAX = 1

        SET     @Day = DATEADD(dd,0,DATEDIFF(dd,0,@TimeOut))
        SET     @HourOfTheDay = DATEPART (HOUR, @TimeOut) 
        SET     @MinuteOfTheHour = DATEPART (MINUTE, @TimeOut)  

        SELECT  @HourlyStatsExists = COUNT(*)
        FROM    dbo.DataWarehouse_HourlyStats
        WHERE   [Day] = @Day
            AND HourOfTheDay = @HourOfTheDay
            AND MinuteOfTheHour = @MinuteOfTheHour

        IF      @HourlyStatsExists = 0
        BEGIN

                INSERT INTO dbo.DataWarehouse_HourlyStats
                (
                 HourOfTheDay, 
                 MinuteOfTheHour, 
                 [Day], 
                 Total
                )
                VALUES (
                 @HourOfTheDay, 
                 @MinuteOfTheHour, 
                 @Day, 
                 @NoOfPAX 
                )

        END
        ELSE
        BEGIN    


                UPDATE  DataWarehouse_HourlyStats
                SET     Total = Total + @NoOfPAX,
                        LastUpdate = GetDate()

                WHERE   [Day] = @Day
                    AND HourOfTheDay = @HourOfTheDay
                    AND MinuteOfTheHour = @MinuteOfTheHour

        END

1 Ответ

5 голосов
/ 05 сентября 2011

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

Шаг 1. Обновите существующие

;WITH x AS 
(
    SELECT d = DATEADD(DAY, 0, DATEDIFF(DAY, 0, i.TimeOut)),
           h = DATEPART(HOUR,   i.TimeOut),
           m = DATEPART(MINUTE, i.Timeout)
    FROM inserted
),
y AS 
(
    SELECT d, h, m, Total = COUNT(*)
    FROM x GROUP BY d, h, m
)
UPDATE h
    SET Total += y.Total,
    LastUpdate = CURRENT_TIMESTAMP        
FROM dbo.DataWarehouse_HourlyStats AS h
INNER JOIN y
    ON h.[Day] = y.d
    AND h.HourOfTheDay = y.h
    AND h.MinuteOfTheHour = y.m;

Шаг 2. Вставьте те, которые не

;WITH x AS 
(
    SELECT d = DATEADD(DAY, 0, DATEDIFF(DAY, 0, i.TimeOut)),
           h = DATEPART(HOUR,   i.TimeOut),
           m = DATEPART(MINUTE, i.Timeout)
    FROM inserted
),
y AS 
(
    SELECT d, h, m, Total = COUNT(*)
    FROM x WHERE NOT EXISTS
    (
      SELECT 1 FROM dbo.DataWarehouse_HourlyStats
      WHERE [Day] = x.d
      AND HourOfTheDay = x.h
      AND MinuteOfTheHour = x.m
    ) 
    GROUP BY d, h, m
)
INSERT dbo.DataWarehouse_HourlyStats
(
    HourOfTheDay, 
    MinuteOfTheHour, 
    [Day],
    Total
)
SELECT h,m,d,Total
    FROM y;

Похоже, больше кода, но я уверяю вас, это более эффективно и более точно, чем ваша существующая версия.

Тем не менее, это будет довольно дорогой триггер, независимо от того, есть ли у васмного вставок.Я надеюсь, что, по крайней мере, в столбцах «День», «Час», «Минута» есть хороший индекс поддержки.

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

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