Firebird Trigger Deadlock - PullRequest
       8

Firebird Trigger Deadlock

0 голосов
/ 16 октября 2011

У меня очень мало опыта работы с базами данных, и я столкнулся с проблемой триггера, вызывающего тупик в базе данных Firebird 2.5.

В базе данных есть две таблицы. Когда элементы добавляются или удаляются из таблицы ITEMS, триггер обновляет STATS.ITEMCOUNT & STATS.SIZE. Всего имеется 4 триггера: 2 с увеличением и 2 с понижением.

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

В первые несколько минут после запуска приложения возникает тупик.

ОБНОВЛЕНИЕ1: опубликованы все триггеры.

ОБНОВЛЕНИЕ2: Опубликован метод ExecuteNonQuery

ОБНОВЛЕНИЕ3: Блокировки по-прежнему возникают даже при использовании метода просмотра, любезно предложенного pilcrow . На самом деле я даже пытался использовать хранимую процедуру, которая снова зашла в тупик. Завершение оператора select в транзакции также не удалось, поскольку поставщик Firebird Ado не поддерживает параллельные транзакции.

public void ExecuteNonQuery(string NonQuery)
        {
            try
            {
                FbCommand FBC = new FbCommand(NonQuery, DBConnection);
                FBC.ExecuteNonQuery();
                FBC.Dispose();
            }
            catch (FbException e)
            {
                Log.FatalException("Database NonQuery Error", e);
            }

        }

        }

База данных

** Tables **

CREATE TABLE ITEMS (
    ID              ID NOT NULL /* ID = VARCHAR(36) NOT NULL */,
    EXPIRYTIME      EXPIRYTIME NOT NULL /* EXPIRYTIME = BIGINT NOT NULL */,
    ITEMSIZE        ITEMSIZE /* ITEMSIZE = BIGINT NOT NULL */,
    ACCESSCOUNT     ACCESSCOUNT DEFAULT 1 NOT NULL /* ACCESSCOUNT = INTEGER DEFAULT 1 NOT NULL */,
    LASTACCESSTIME  LASTACCESSTIME /* LASTACCESSTIME = TIMESTAMP NOT NULL */
);


CREATE TABLE STATS (
    INSTANCE            SMALLINT,
    SIZE                BIGINT DEFAULT 0,
    ITEMCOUNT  BIGINT DEFAULT 0,
    HITS       BIGINT DEFAULT 0,
    MISSES     BIGINT DEFAULT 0
);

** Триггеры **

   /* Trigger: TRG_INCREMENT_ITEMCOUNT_STATS */
CREATE OR ALTER TRIGGER TRG_INCREMENT_ITEMCOUNT_STATS FOR ITEMS
ACTIVE AFTER INSERT POSITION 1
AS
begin
 UPDATE STATS SET ITEMCOUNT = ITEMCOUNT + 1 WHERE INSTANCE = '0';
end

/* Trigger: TRG_DECREMENT_ITEMCOUNT_STATS */
CREATE OR ALTER TRIGGER TRG_DECREMENT_ITEMCOUNT_STATS FOR ITEMS
ACTIVE AFTER DELETE POSITION 2
AS
begin
UPDATE STATS SET ITEMCOUNT = ITEMCOUNT - 1 WHERE INSTANCE = '0';
end

/* Trigger: TRG_INCREMENT_HITS_STATS */
CREATE OR ALTER TRIGGER TRG_INCREMENT_HITS_STATS FOR ITEMS
ACTIVE AFTER UPDATE POSITION 3
AS
begin
UPDATE STATS SET HITS = HITS + 1 WHERE INSTANCE = '0';
end

/* Trigger: TRG_INCREMENT_SIZE_STATS */
CREATE OR ALTER TRIGGER TRG_INCREMENT_SIZE_STATS FOR ITEMS
ACTIVE AFTER INSERT POSITION 4
AS
BEGIN
  UPDATE STATS SET SIZE = SIZE + NEW.ITEMSIZE WHERE INSTANCE = 0;
END

/* Trigger: TRG_DECREMENT_CACHESIZE_STATS */
CREATE OR ALTER TRIGGER TRG_DECREMENT_CACHESIZE_STATS FOR ITEMS
ACTIVE AFTER DELETE POSITION 5
AS
BEGIN
  UPDATE STATS SET SIZE = SIZE - OLD.ITEMSIZE WHERE INSTANCE = 0;
END

Ответы [ 4 ]

1 голос
/ 16 октября 2011

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

Другой способ потребовал бы перепроектирования таблицы с полной информацией.

0 голосов
/ 24 октября 2011

Исправлено с помощью

.IsolationLevel = IsolationLevel.ReadUncommitted;

в строке подключения.

0 голосов
/ 20 октября 2011

Эти проблемы обычно легче решить, используя запись на изменение с +1 или -1, а иногда (ежедневно, еженедельно) обрабатывая всю таблицу, чтобы подвести итоги и получить только одну запись снова. Следующим изменением будет снова запись +1 или -1, и вы запросите итоговую сумму.

То есть у вас будет что-то вроде:

ITEM  COUNT
item1 10
item2 10
item1 1
item2 -1
item2 -1
item1 -1

и после запланированного слияния вы получите:

ITEM   COUNT
item1  10
item2  8

Затем можно добавить представление, которое просто суммирует записи по элементу.

0 голосов
/ 17 октября 2011

Вы пишете:

... есть ли раунд работы.

Существует как минимум еще один подход. Вместо использования триггеров для предварительного вычисления агрегированных подсчетов и размеров, вы могли бы просто вычислить их по запросу в VIEW следующим образом:

CREATE VIEW stats (instance, size, itemcount, hits, misses) AS
   SELECT CAST(0 AS SMALLINT),
          CAST(COALESCE(SUM(items.itemsize), 0) AS BIGINT),
          CAST(COUNT('x') AS BIGINT),
          CAST(COALESCE(SUM(items.accesscount), 0) AS BIGINT), -- just guessing here
          0                                                    -- but see below
     FROM items;

(Примечание: я предполагаю, что ваши HITS представляют собой сумму ITEMS.ACCESSCOUNT, как показывают имена триггеров и столбцов UPDATE. Вы не сообщаете нам, как записываете MISSES сейчас, но если приложение напрямую увеличивая STATS.MISSES в настоящее время, вы можете ввести для этой цели новую таблицу и затем присоединиться к этой таблице в VIEW выше.)

Конечно, вам все равно придется надлежащим образом фиксировать транзакции. Представленное выше представление STATS является настолько же точным, как и завершенные, зафиксированные транзакции, которые оно может видеть.

...