2013-08-18 34 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;