postgresql тупик - PullRequest
       29

postgresql тупик

5 голосов
/ 26 января 2012

Иногда postgresql вызывает ошибки блокировки.

В триггере для таблицы, установленной для ОБНОВЛЕНИЯ.

Комментарий к таблице:

http://pastebin.com/L1a8dbn4

Журнал (вставленные предложения обрезаны):

2012-01-26 17:21:06 MSK ERROR:  deadlock detected
2012-01-26 17:21:06 MSK DETAIL:  Process 2754 waits for ExclusiveLock on tuple (40224,15) of relation 735493 of database 734745; blocked by process 2053.
Process 2053 waits for ShareLock on transaction 25162240; blocked by process 2754.
Process 2754: INSERT INTO comment (user_id, content_id, reply_id, text) VALUES (1756235868, 935967, 11378142, 'text1') RETURNING comment.id;
Process 2053: INSERT INTO comment (user_id, content_id, reply_id, text) VALUES (4071267066, 935967, 11372945, 'text2') RETURNING comment.id;
2012-01-26 17:21:06 MSK HINT:  See server log for query details.
2012-01-26 17:21:06 MSK CONTEXT:  SQL statement "SELECT comments_count FROM content WHERE content.id = NEW.content_id FOR UPDATE"
PL/pgSQL function "increase_comment_counter" line 5 at SQL statement
2012-01-26 17:21:06 MSK STATEMENT:  INSERT INTO comment (user_id, content_id, reply_id, text) VALUES (1756235868, 935967, 11378142, 'text1') RETURNING comment.id;

И вызвать комментарий к таблице:

CREATE OR REPLACE FUNCTION increase_comment_counter() RETURNS TRIGGER AS $$
DECLARE
comments_count_var INTEGER;
BEGIN
    SELECT INTO comments_count_var comments_count FROM content WHERE content.id = NEW.content_id FOR UPDATE;
    UPDATE content SET comments_count = comments_count_var + 1, last_comment_dt = now()  WHERE content.id = NEW.content_id;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;



CREATE TRIGGER increase_comment_counter_trigger AFTER INSERT ON comment FOR EACH ROW EXECUTE PROCEDURE increase_comment_counter();

Почему это может произойти?

Спасибо!

1 Ответ

10 голосов
/ 26 января 2012

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

Однако затем триггер продолжает обновлять блокировку до ИСКЛЮЧИТЕЛЬНОЙ, и это может быть заблокировано одновременной транзакцией, выполняющей тот же процесс. Рассмотрим следующую последовательность событий:

Txn 2754                      Txn 2053
Insert Comment
                              Insert Comment
Lock Content#935967 SHARE
  (performed by fkey)
                              Lock Content#935967 SHARE
                                (performed by fkey)
Trigger
Lock Content#935967 EXCLUSIVE
(blocks on 2053's share lock)
                              Trigger
                              Lock Content#935967 EXCLUSIVE
                              (blocks on 2754's share lock)

Так что тупик.

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

SELECT 1 FROM content WHERE content.id = 935967 FOR UPDATE
INSERT INTO comment(.....)

Другое решение состоит в том, чтобы просто полностью избежать этой схемы «кэшированных подсчетов», кроме случаев, когда вы можете доказать, что это необходимо для производительности. Если это так, подумайте о том, чтобы сохранить кэшированный счет где-то, кроме таблицы содержимого, например выделенный стол для прилавка. Это также сократит трафик обновлений к таблице контента при каждом добавлении комментария. Или, может быть, просто повторно выберите количество и используйте memcached в приложении. Нельзя обойти стороной тот факт, что где бы вы ни хранили этот кэшированный счетчик, он будет задыхаться, его нужно безопасно обновлять.

...