sql >> データベース >  >> RDS >> PostgreSQL

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

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

    TABLE vm_action_info
    ==>外部キー(last_completed_vm_task_id)参照vm_task(id)

    テーブルvm_task
    ==>外部キー(vm_action_info_id)参照vm_action_info(id)

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

    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;
    


    1. 外部サービスでログインする

    2. PostgreSQLは2つのSQLServerストアドプロシージャ間の結合を実行できますか?

    3. 更新時のSQLエラー:UPDATEステートメントがFOREIGNKEY制約と競合していました

    4. PHPでpdoを使用してデータベースを作成する