Thread: BUG #5326: The 2nd update of a table which has foreign keys is blocked.

BUG #5326: The 2nd update of a table which has foreign keys is blocked.

From
"Sadao Hiratsuka"
Date:
The following bug has been logged online:

Bug reference:      5326
Logged by:          Sadao Hiratsuka
Email address:      sh2@pop01.odn.ne.jp
PostgreSQL version: 8.4.2
Operating system:   Linux x86
Description:        The 2nd update of a table which has foreign keys is
blocked.
Details:

The 2nd update of a table which has foreign keys is blocked.

<test case 1>
create table parent (k int primary key, d varchar(10));
create table child (k int primary key, f int, d varchar(10),
    constraint child_fk1 foreign key (f) references parent (k));

insert into parent values (1, 'a');
insert into parent values (2, 'b');

insert into child values (11, 1, 'aa');
insert into child values (12, 2, 'bb');

client1> begin;
client1> update parent set d = 'a2' where k = 1;

client2> begin;
client2> update child set d = 'aa2' where k = 11; -- ok
client2> update child set d = 'aa3' where k = 11; -- blocked

Especially, a deadlock occurs by the following sequence.

<test case 2>
client1> begin;
client1> update parent set d = 'a2' where k = 1;

client2> begin;
client2> update parent set d = 'b2' where k = 2;
client2> update parent set d = 'a3' where k = 1; -- blocked

client1> update child set d = 'bb2' where k = 12; -- ok
client1> update child set d = 'bb3' where k = 12; -- deadlock

Is this PostgreSQL's bug? or right behavior?

Thanks,

Re: BUG #5326: The 2nd update of a table which has foreign keys is blocked.

From
Takahiro Itagaki
Date:
"Sadao Hiratsuka" <sh2@pop01.odn.ne.jp> wrote:

> PostgreSQL version: 8.4.2
> The 2nd update of a table which has foreign keys is blocked.
>
> <test case 1>
> create table parent (k int primary key, d varchar(10));
> create table child (k int primary key, f int, d varchar(10),
>     constraint child_fk1 foreign key (f) references parent (k));
>
> insert into parent values (1, 'a');
> insert into parent values (2, 'b');
>
> insert into child values (11, 1, 'aa');
> insert into child values (12, 2, 'bb');
>
> client1> begin;
> client1> update parent set d = 'a2' where k = 1;
>
> client2> begin;
> client2> update child set d = 'aa2' where k = 11; -- ok
> client2> update child set d = 'aa3' where k = 11; -- blocked

The limitation still exists even in HEAD.
(Sorry for the wrong report in another mail, Hiratsuka-san.)

The comment in AfterTriggerSaveEvent() in commands/trigger.c says we
cannot skip FK checks when we update the same tuple in one transaction.

/*
 * Update on FK table
 *
 * There is one exception when updating FK tables: if the
 * updated row was inserted by our own transaction and the
 * FK is deferred, we still need to fire the trigger. This
 * is because our UPDATE will invalidate the INSERT so the
 * end-of-transaction INSERT RI trigger will not do
 * anything, so we have to do the check for the UPDATE
 * anyway.
 */
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(oldtup->t_data)) &&
    RI_FKey_keyequal_upd_fk(trigger, rel, oldtup, newtup))
{
    continue;   <== skip the FK check
}

But to be exact, the comment says we *can* still skip the checks
if we don't have any deferred FKs, right?  If so, can we add
a "has_deferred_FKs()" check to the condition?

if ((!has_deferred_FKs(rel) ||
     !TransactionIdIsCurrentTransactionId(...)) &&
    RI_FKey_keyequal_upd_fk(...)


Regards,
---
Takahiro Itagaki
NTT Open Source Software Center

Re: BUG #5326: The 2nd update of a table which has foreign keys is blocked.

From
Tom Lane
Date:
Takahiro Itagaki <itagaki.takahiro@oss.ntt.co.jp> writes:
> But to be exact, the comment says we *can* still skip the checks
> if we don't have any deferred FKs, right?  If so, can we add
> a "has_deferred_FKs()" check to the condition?

> if ((!has_deferred_FKs(rel) ||
>      !TransactionIdIsCurrentTransactionId(...)) &&
>     RI_FKey_keyequal_upd_fk(...)

No.  That wouldn't fix this problem (since the OP hasn't got any
deferred triggers) and it would break the test we need to make
(since the deferred FK isn't necessarily *this* FK, and even if
it was, what you propose would prevent it from being checked).

If we knew that the just-outdated tuple had been created by an update
that didn't change the FK columns, then we could skip applying the
check at the new update.  But we don't know that, and I don't see any
real easy way to shoehorn in the knowledge.  We don't have any extra
per-tuple state here --- and per-tuple state wouldn't be good enough
anyway, if there are multiple FKs.  Another possibility is to chain
back to the latest tuple version that existed prior to this transaction
and compare FK columns against that version ... except we have no good
way to do that either; the t_ctid links point the wrong way.

AFAICS there is no simple way to improve this.  It's an optimization
that the first update didn't block.  We can't easily extend that
optimization to the second update.  Sorry.

            regards, tom lane