2013-08-18 37 views
14

我正在使用postgres 9.1并在过度执行一个简单的更新方法下得到死锁异常。postgres在简单更新查询中的死锁

根据日志,死锁发生是由于同时执行两个相同的更新。

更新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约束,因为我不完全确定它会如何影响系统。在代码中添加一个限制,只有单个查询可以在给定的时间执行,解决了这个问题,但我不明白查询如何导致自身死锁。所有的锁都是以相同的顺序获得的,所以它不应该发生。 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秒,否则我们不会看到超时。 Timout到期了,死锁检测器看一看,看到一堆交织在一起的交易并取消其中一个交易。可能不是严格意义上的僵局,但我不确定探测器是否足够聪明可以搞清楚。

尝试之一:

  1. 减少更新的速度
  2. 获得速度更快的服务器
  3. 增加DEADLOCK_TIMEOUT

大概#3是:-)可能要设置的最简单log_lock_waits也是这样,你可以看到你的系统是否处于这种紧张状态下。

+0

在较慢的更新速率这种情况不会发生。关于#3建议:根据postgres文档,deadloak_timeout参数只定义了执行死锁检测机制之前的时间量,并且不影响是否声明死锁情况。从文档:“他是等待锁定的时间,以毫秒为单位,然后检查是否存在死锁条件。检查死锁是相对昂贵的,所以服务器不会每次运行它等待锁定“ – moshe

+0

升级到版本9。2也可能有所帮助,它在锁定行为和总体速度方面有一些改进。 –

+0

条件是死锁,除非其中一个事务被中止。 –

14

我的猜测是问题的根源是表中的循环外键引用。

TABLE vm_action_info
==>外键(last_completed_vm_task_id)参考vm_task(ID)

TABLE vm_task
==>外键(vm_action_info_id)参考vm_action_info(ID)

事务包括两个步骤:

  1. 添加新条目任务表表相应vm_action_in进入
  2. 更新对于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=> 

然后第一个事务执行upd吃:

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=> 

死锁检测到!

这是因为对vm_task的两个INSERT由于外键引用而对vm_action_info表中的行id = 2放置共享锁。然后,第一次更新尝试在该行上放置写入锁并挂起,因为该行被另一个(第二个)事务锁定。然后,第二次更新尝试在写入模式下锁定相同的记录,但第一次事务锁定在共享模式下。这导致了僵局。

我认为,如果你把一个写锁纪录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;