Тупик в postgres для простого запроса на обновление
Я работаю с postgres 9.1 и получаю исключение блокировки при чрезмерном выполнении простого метода обновления.
Согласно журналам, тупик возникает из-за одновременного выполнения двух идентичных обновлений.
update public.vm_action_info set 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 );
Ответы
Ответ 1
Моя догадка заключается в том, что источником проблемы является круговая ссылка внешнего ключа в ваших таблицах.
TABLE vm_action_info
== > FOREIGN KEY (last_completed_vm_task_id) ССЫЛКИ vm_task (id)
TABLE vm_task
== > FOREIGN KEY (vm_action_info_id) ССЫЛКИ vm_action_info (id)
Транзакция состоит из двух шагов:
- добавить новую запись в таблицу задач
- обновляет соответствующую запись в vm_action_info таблице 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, ссылающуюся на id = 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;
Ответ 2
Возможно, ваша система была исключительно занята. Вы говорите, что видели это только с "чрезмерным исполнением" запроса.
Похоже, ситуация такова:
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 секунд, иначе мы не увидели бы тайм-аут. Таймер истекает, детектор блокировки смотрит, видит кучу переплетенных транзакций и отменяет один из них. Возможно, это не тупик, но я не уверен, что детектор достаточно умен, чтобы понять это.
Попробуйте выполнить одно из следующих действий:
- Уменьшить скорость обновления
- Получить более быстрый сервер
- Увеличить deadlock_timeout
Вероятно, что № 3 является самым простым:-) Возможно, вы захотите установить log_lock_waits, чтобы вы могли видеть, когда/когда ваша система находится под таким напряжением.