Deadlock. Referential Integrity checks select for update? - Mailing list pgsql-general

From Grant McLean
Subject Deadlock. Referential Integrity checks select for update?
Date
Msg-id 1088138427.17661.41.camel@putnam
Whole thread Raw
Responses Re: Deadlock. Referential Integrity checks select for update?
List pgsql-general
Hi

First a simple question ...

I have a table "access_log" that has foreign keys "app_id" and
"app_user_id" that reference the "application_type" and "app_user"
tables.  When I insert into "access_log", the referential integrity
triggers generate these queries:

  SELECT 1 FROM ONLY "public"."application_type" x
  WHERE "app_id" = $1 FOR UPDATE OF x

  SELECT 1 FROM ONLY "public"."app_user" x
  WHERE "app_user_id" = $1 FOR UPDATE OF x

I don't understand why these two queries include "FOR UPDATE".  Can
anyone explain that?


That question may in fact be a red herring as far as my real problem
goes, so I'll describe that too ...

I have a web application in which transactions die with database
deadlocks under load.  I have isolated the problem to when two
different types of transaction occur in parallel:

  Transaction A:
    create a new record in "session" and get its ID then
    create a new record in "access_log" and get its ID

  Transaction B:
    retrieve an existing record from "session" using its ID then
    create a new record in "access_log" and get its ID

In practice, the initial select from session in transaction B doesn't
affect the deadlock, so I've omitted it here.

The theoretical parallel sequence of queries is:

  A1 insert_session         B1 insert_access_log
  A2 get_session_id         B2 get_access_log_id
  A3 insert_access_log      B3 commit
  A4 get_access_log_id
  A5 commit

The postgresql log show this is the sequence that causes deadlock:

  A1  insert_session
  riA  select from app_user for update
                            B1  insert_access_log
                            riB  select from app_type for update
                            riB  select from app_user for update
                            << Process B blocked >>
  A2  get_session_id
  A3  insert_access_log
  riA  select from app_type for update
                       << DEADLOCK >>
  << Process A killed >>

"riA" and "riB" refer to system-generated statements which check
referential integrity.

Neither transaction A nor transaction B ever updates either the
application_type or app_user tables and the deadlock only seems
to happen as a result of the referential integrity checks locking
the parent tables for update.  So it seems that even if I'm careful
to always do my updates in a consistent order to avoid deadlock,
the referential integrity constraints can cause deadlock themselves.


Here's the snippet from the postgresql log:

15:37:15 [20480] LOG:  statement: select getdatabaseencoding()
15:37:15 [20480] LOG:  statement: begin
15:37:15 [20481] LOG:  statement: select getdatabaseencoding()
15:37:15 [20481] LOG:  statement: begin
15:37:40 [20480] LOG:  statement: INSERT INTO session (app_user_id,
session_data) VALUES ('1', '')
15:37:40 [20480] LOG:  statement: SELECT 1 FROM ONLY "public"."app_user"
x WHERE "app_user_id" = $1 FOR UPDATE OF x

15:37:51 [20481] LOG:  statement:
  INSERT INTO access_log (app_id, app_user_id, log_data)
  VALUES ('1', '1', 'Log data here')

15:37:51 [20481] LOG:  statement: SELECT 1 FROM ONLY
"public"."application_type" x WHERE "app_id" = $1 FOR UPDATE OF x
15:37:51 [20481] LOG:  statement: SELECT 1 FROM ONLY "public"."app_user"
x WHERE "app_user_id" = $1 FOR UPDATE OF x

15:38:04 [20480] LOG:  statement: SELECT currval('seq_session_id')

15:38:17 [20480] LOG:  statement:
  INSERT INTO access_log (app_id, app_user_id, log_data)
  VALUES ('1', '1', 'Log data here')

15:38:17 [20480] LOG:  statement: SELECT 1 FROM ONLY
"public"."application_type" x WHERE "app_id" = $1 FOR UPDATE OF x

15:38:20 [20480] ERROR:  deadlock detected
DETAIL:  Process 20480 waits for ShareLock on transaction 74278; blocked
by process 20481.
    Process 20481 waits for ShareLock on transaction 74275; blocked by
process 20480.
15:38:20 [20480] LOG:  statement: rollback




And here's a short script to create the database:

#!/bin/sh

DBNAME=deadpool

if psql -l | grep -q $DBNAME
then
    dropdb $DBNAME || exit
fi

createdb $DBNAME

psql -d $DBNAME <<EOF

  create sequence seq_access_log_id;

  create sequence seq_session_id;

  create table app_user (
    app_user_id          INT4   not null,
    login_name           TEXT   not null,
    constraint PK_APP_USER primary key (app_user_id)
  );

  create table application_type (
    app_id               INT4   not null,
    app_desc             TEXT   not null,
    constraint PK_APPLICATION_TYPE primary key (app_id)
  );

  create table session (
    session_id           INT8   not null default
nextval('seq_session_id'),
    app_user_id          INT4   not null,
    session_data         TEXT   not null,
    constraint PK_SESSION primary key (session_id),
    constraint FK_SESS_REFERENCE_APP_ foreign key (app_user_id)
      references app_user (app_user_id)
      deferrable
  );

  create table access_log (
    access_log_id        INT8   not null default
nextval('seq_access_log_id'),
    app_id               INT4   not null,
    app_user_id          INT4   not null,
    log_data             TEXT   null,
    constraint PK_ACCESS_LOG primary key (access_log_id),
    constraint FK_ACCE_REFERENCE_APPL foreign key (app_id)
      references application_type (app_id)
      deferrable,
    constraint FK_ACCE_REFERENCE_APP_ foreign key (app_user_id)
      references app_user (app_user_id)
      deferrable
  );

  create index access_log_app_type_fk on access_log (
    app_id
  );

  create index access_log_app_user_id on access_log (
    app_user_id
  );

  insert into application_type values (1, 'Public Web App');

  insert into app_user values (1, 'anonymous');

EOF


I also have a Perl script that steps through the two transactions in the
appropriate order and causes the deadlock every time.


Any assistance anyone can offer would be appreciated.

Regards
Grant



pgsql-general by date:

Previous
From: Edwin New
Date:
Subject: Running two postmasters on one host: could not create semaphores
Next
From: Tom Lane
Date:
Subject: Re: Running two postmasters on one host: could not create semaphores