2013-08-18 2 views
14

Я работаю с postgres 9.1 и получаю исключение блокировки при чрезмерном выполнении простого метода обновления.deadlock in postgres on simple update query

Согласно журналам, тупик возникает из-за одновременного выполнения двух идентичных обновлений.

обновление public.vm_action_info набор last_on_demand_task_id = $ 1, версия = версия + 1

Как два одинаковых простые обновления могут тупиковой друг с другом?

ошибка, что я получаю в журнале

2013-08-18 11:00:24 IDT HINT: See server log for query details. 
2013-08-18 11:00:24 IDT STATEMENT: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 
2013-08-18 11:00:25 IDT ERROR: deadlock detected 
2013-08-18 11:00:25 IDT DETAIL: Process 31533 waits for ShareLock on transaction 4228275; blocked by process 31530. 
     Process 31530 waits for ExclusiveLock on tuple (0,68) of relation 70337 of database 69205; blocked by process 31533. 
     Process 31533: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 
     Process 31530: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 
2013-08-18 11:00:25 IDT HINT: See server log for query details. 
2013-08-18 11:00:25 IDT STATEMENT: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 
2013-08-18 11:00:25 IDT ERROR: deadlock detected 
2013-08-18 11:00:25 IDT DETAIL: Process 31530 waits for ExclusiveLock on tuple (0,68) of relation 70337 of database 69205; blocked by process 31876. 
     Process 31876 waits for ShareLock on transaction 4228275; blocked by process 31530. 
     Process 31530: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 
     Process 31876: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 

схема является:

CREATE TABLE vm_action_info(
    id integer NOT NULL, 
    version integer NOT NULL DEFAULT 0, 
    vm_info_id integer NOT NULL, 
last_exit_code integer, 
    bundle_action_id integer NOT NULL, 
    last_result_change_time numeric NOT NULL, 
    last_completed_vm_task_id integer, 
    last_on_demand_task_id bigint, 
    CONSTRAINT vm_action_info_pkey PRIMARY KEY (id), 
    CONSTRAINT vm_action_info_bundle_action_id_fk FOREIGN KEY (bundle_action_id) 
     REFERENCES bundle_action (id) MATCH SIMPLE 
     ON UPDATE NO ACTION ON DELETE CASCADE, 
    CONSTRAINT vm_discovery_info_fk FOREIGN KEY (vm_info_id) 
     REFERENCES vm_info (id) MATCH SIMPLE 
     ON UPDATE NO ACTION ON DELETE CASCADE, 
    CONSTRAINT vm_task_last_on_demand_task_fk FOREIGN KEY (last_on_demand_task_id) 
     REFERENCES vm_task (id) MATCH SIMPLE 
     ON UPDATE NO ACTION ON DELETE NO ACTION, 

    CONSTRAINT vm_task_last_task_fk FOREIGN KEY (last_completed_vm_task_id) 
     REFERENCES vm_task (id) MATCH SIMPLE 
     ON UPDATE NO ACTION ON DELETE NO ACTION 
) 
WITH (OIDS=FALSE); 

ALTER TABLE vm_action_info 
    OWNER TO vadm; 

-- Index: vm_action_info_vm_info_id_index 

-- DROP INDEX vm_action_info_vm_info_id_index; 

CREATE INDEX vm_action_info_vm_info_id_index 
    ON vm_action_info 
    USING btree (vm_info_id); 

CREATE TABLE vm_task 
(
    id integer NOT NULL, 
    version integer NOT NULL DEFAULT 0, 
    vm_action_info_id integer NOT NULL, 
    creation_time numeric NOT NULL DEFAULT 0, 
    task_state text NOT NULL, 
    triggered_by text NOT NULL, 
    bundle_param_revision bigint NOT NULL DEFAULT 0, 
    execution_time bigint, 
    expiration_time bigint, 
    username text, 
    completion_time bigint, 
    completion_status text, 
    completion_error text, 
    CONSTRAINT vm_task_pkey PRIMARY KEY (id), 
    CONSTRAINT vm_action_info_fk FOREIGN KEY (vm_action_info_id) 
    REFERENCES vm_action_info (id) MATCH SIMPLE 
    ON UPDATE NO ACTION ON DELETE CASCADE 
) 
WITH (
OIDS=FALSE 
); 
ALTER TABLE vm_task 
    OWNER TO vadm; 

-- Index: vm_task_creation_time_index 

-- DROP INDEX vm_task_creation_time_index  ; 

CREATE INDEX vm_task_creation_time_index 
    ON vm_task 
    USING btree 
(creation_time); 
+0

Они не так просто. На поле есть FK-константа (что приводит к необходимости обновления индекса). Может быть, отложить отсрочку первоначально? (не думаю, что это может иметь значение) – wildplasser

+1

Я предпочитаю не менять ограничение FK, поскольку я не совсем уверен, как это повлияет на систему в genral. Добавление ограничения в код, который может выполнять только один запрос в заданное время, решает проблему, но я не понимаю, как запрос может вызвать тупик с самим собой. Все замки приобретаются в том же порядке, что и теоретически, этого не должно быть. Есть ли вероятность того, что postgres поразительно обнаруживает тупик, который на самом деле не существует? – moshe

+0

Вы написали: «Все блокировки получены в том же порядке», означает ли это, что это не просто обновление, но вся транзакция состоит из большего количества команд блокировки, чем это одно обновление? Если да, то, пожалуйста, покажите нам весь код. – krokodilko

ответ

4

Это может быть просто, что ваша система была исключительно занята. Вы говорите, что видели это только с «чрезмерным исполнением» запроса.

Что, как представляется, ситуация такова:

pid=31530 wants to lock tuple (0,68) on rel 70337 (vm_action_info I suspect) for update 
    it is waiting behind pid=31533, pid=31876 
pid=31533 is waiting behind transaction 4228275 
pid=31876 is waiting behind transaction 4228275 

Итак - мы имеем то, что, кажется, четыре операции все обновление этой строки в то же время. Транзакция 4228275 еще не совершила или откатилась назад и удерживает остальных. Двое из них ждали deadlock_timeout секунд, иначе мы не увидели бы тайм-аут. Таймер истекает, детектор блокировки смотрит, видит кучу переплетенных транзакций и отменяет один из них. Возможно, это не тупик, но я не уверен, что детектор достаточно умен, чтобы понять это.

Попробуйте один из:

  1. Снизить скорость обновлений
  2. Получить быстрый сервер
  3. Увеличение deadlock_timeout

Возможно # 3 это самый простой :-) Может хотите установить log_lock_waits, чтобы вы могли видеть, работает ли ваша система под таким типом напряжения.

+0

При более медленной скорости обновления этого сценария не происходит. Что касается предложения № 3: согласно документации postgres параметр deadloak_timeout определяет время, прежде чем механизм обнаружения блокировки будет выполнен и не повлияет на то, объявлена ​​ли ситуация блокировки. из документации: «его количество времени в миллисекундах, чтобы ждать блокировки, прежде чем проверять, есть ли условие взаимоблокировки. Проверка на взаимоблокировку относительно дорога, поэтому сервер не запускает ее каждый раз, когда она ждет блокировки " – moshe

+0

Обновление до версии 9.2 также может помочь, он имеет несколько улучшений в отношении поведения блокировки и общей скорости. –

+0

Условие является взаимоблокировкой, если только одна из транзакций не прервана. –

14

Моя догадка заключается в том, что источником проблемы является круговая ссылка внешнего ключа в ваших таблицах.

ТАБЛИЦА vm_action_info
==> FOREIGN KEY (last_completed_vm_task_id) ссылки vm_task (ID)

ТАБЛИЦА vm_task
==> FOREIGN KEY (vm_action_info_id) СПИСОК ЛИТЕРАТУРЫ vm_action_info (ID)

сделка состоит из двух этапов:

  1. добавить новую запись к задаче таблицу
  2. обновления, соответствующие записи в vm_action_in для таблицы vm_task.

Когда две сделки собираются обновить ту же запись в таблице vm_action_info в то же время, это закончится тупик.

Посмотрите на простой тест:

CREATE TABLE vm_task 
(
    id integer NOT NULL, 
    version integer NOT NULL DEFAULT 0, 
    vm_action_info_id integer NOT NULL, 
    CONSTRAINT vm_task_pkey PRIMARY KEY (id) 
) 
WITH (OIDS=FALSE); 

insert into vm_task values 
(0, 0, 0), (1, 1, 1), (2, 2, 2); 

CREATE TABLE vm_action_info(
    id integer NOT NULL, 
    version integer NOT NULL DEFAULT 0, 
    last_on_demand_task_id bigint, 
    CONSTRAINT vm_action_info_pkey PRIMARY KEY (id) 
) 
WITH (OIDS=FALSE); 
insert into vm_action_info values 
(0, 0, 0), (1, 1, 1), (2, 2, 2); 

alter table vm_task 
add CONSTRAINT vm_action_info_fk FOREIGN KEY (vm_action_info_id) 
    REFERENCES vm_action_info (id) MATCH SIMPLE 
    ON UPDATE NO ACTION ON DELETE CASCADE 
    ; 
Alter table vm_action_info 
add CONSTRAINT vm_task_last_on_demand_task_fk FOREIGN KEY (last_on_demand_task_id) 
     REFERENCES vm_task (id) MATCH SIMPLE 
     ON UPDATE NO ACTION ON DELETE NO ACTION 
     ; 


В сессии 1 добавить запись в vm_task, что ссылка на ид = 2 в vm_action_info

session1=> begin; 
BEGIN 
session1=> insert into vm_task values(100, 0, 2); 
INSERT 0 1 
session1=> 

В то же время в сессии 2 начинается другая транзакция:

session2=> begin; 
BEGIN 
session2=> insert into vm_task values(200, 0, 2); 
INSERT 0 1 
session2=> 

Затем первая транзакция выполняет обновление ели:

session1=> update vm_action_info set last_on_demand_task_id=100, version=version+1 
session1=> where id=2; 

, но эта команда зависаний и ждет замок .....

то вторая сессия выполняет обновление ........

session2=> update vm_action_info set last_on_demand_task_id=200, version=version+1 where id=2; 
BŁĄD: wykryto zakleszczenie 
SZCZEGÓŁY: Proces 9384 oczekuje na ExclusiveLock na krotka (0,5) relacji 33083 bazy danych 16393; zablokowany przez 380 
8. 
Proces 3808 oczekuje na ShareLock na transakcja 976; zablokowany przez 9384. 
PODPOWIEDŹ: Przejrzyj dziennik serwera by znaleźć szczegóły zapytania. 
session2=> 

Тупик обнаружено !!!

Это связано с тем, что оба INSERT в vm_task помещают общую блокировку в строке id = 2 в таблицу vm_action_info из-за ссылки на внешний ключ. Затем первое обновление пытается помещать блокировку записи в эту строку и зависает, потому что строка заблокирована другой (второй) транзакцией. Затем второе обновление пытается заблокировать одну и ту же запись в режиме записи, но первая транзакция заблокирована в режиме совместного доступа. И это вызывает тупик.

Я думаю, что этого можно избежать, если поместить блокировку записи на запись в vm_action_info, вся сделка должна состоять из 5 этапов:

begin; 
select * from vm_action_info where id=2 for update; 
insert into vm_task values(100, 0, 2); 
update vm_action_info set last_on_demand_task_id=100, 
     version=version+1 where id=2; 
commit;