web-dev-qa-db-ja.com

単純な更新クエリでpostgresのデッドロック

私はpostgres 9.1を使用していて、単純な更新メソッドを過度に実行するとデッドロック例外が発生します。

ログによると、2つの同じ更新を同時に実行したためにデッドロックが発生しています。

update public.vm_action_info set last_on_demand_task_id = $ 1、version = version + 1

2つの同一の単純な更新がどのようにして互いにデッドロックする可能性がありますか?

ログに記録されるエラー

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 );
16
moshe

私の推測では、問題の原因はテーブル内の循環外部キー参照です。

TABLE vm_action_info
==> FOREIGN KEY(last_completed_vm_task_id)REFERENCES vm_task(id)

TABLE vm_task
==> FOREIGN KEY(vm_action_info_id)REFERENCES vm_action_info(id)

トランザクションは2つのステップで構成されます。

  1. タスクテーブルに新しいエントリを追加する
  2. vm_action_infoの対応するエントリをvm_taskテーブルで更新します。

2つのトランザクションが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_action_infoのid = 2を参照するレコードをvm_taskに追加します

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;

しかし、このコマンドはハングし、ロックを待機しています。

その後、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に共有ロックを配置するためです。次に、最初の更新はこの行に書き込みロックを配置しようとし、行が別の(2番目の)トランザクションによってロックされているためにハングします。次に、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;
19
krokodilko

システムが非常にビジーであった可能性があります。これは、クエリが「過剰に実行された」場合にのみ見たと言います。

状況に見えるのはこれです:

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

したがって、4つのトランザクションのように見え、すべて同時にこの行を更新しています。トランザクション4228275はまだコミットまたはロールバックされておらず、他のトランザクションを保留しています。それらのうちの2つは deadlock_timeout 秒待機しています。それ以外の場合、タイムアウトは表示されません。 Timoutが期限切れになり、デッドロック検出機能が調べて、絡み合ったトランザクションの束を確認し、それらの1つをキャンセルします。厳密にはデッドロックではないかもしれませんが、検出器がそれを理解するのに十分スマートであるかどうかはわかりません。

次のいずれかを試してください。

  1. 更新率を下げる
  2. より高速なサーバーを入手する
  3. Deadlock_timeoutを増やす

おそらく#3が最も簡単です:-)システムがこの種の負荷にさらされているかどうかを確認できるように、log_lock_waitsも設定する必要があるかもしれません。

3
Richard Huxton