Re: BUG #13148: Unexpected deferred EXCLUDE constraint violation on derived table

From: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
To: postgresql2(at)realityexists(dot)net
Cc: pgsql-bugs(at)postgresql(dot)org, pgsql-hackers(at)postgresql(dot)org
Subject: Re: BUG #13148: Unexpected deferred EXCLUDE constraint violation on derived table
Date: 2015-05-10 20:01:53
Message-ID: 30055.1431288113@sss.pgh.pa.us
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-bugs pgsql-hackers

postgresql2(at)realityexists(dot)net writes:
> I have a deferred EXCLUDE constraint on a derived table. Inside a
> transaction I insert a new row that conflicts with an existing one (so the
> constraint would fail if it was immediate), delete the old row and run an
> unrelated UPDATE on the new row, then try to commit. I would expect the
> commit to succeed, since there is now no conflict, but it fails with
> ERROR: conflicting key value violates exclusion constraint
> "uq_derived_timeslice_dup_time_ex"

Hm. The given test case is overcomplicated; in point of fact it will fail
on any deferred exclusion constraint, eg

DROP TABLE IF EXISTS derived_timeslice CASCADE;

CREATE TABLE derived_timeslice
(
timeslice_id integer NOT NULL,
feature_id integer NOT NULL,
name text,
CONSTRAINT pk_derived_timeslice PRIMARY KEY (timeslice_id),
CONSTRAINT uq_derived_timeslice_dup_time_ex EXCLUDE
USING btree (feature_id WITH =)
DEFERRABLE INITIALLY DEFERRED
);

INSERT INTO derived_timeslice (timeslice_id, feature_id) VALUES (51, 1);

BEGIN;

-- Insert row that violates deferred constraint
INSERT INTO derived_timeslice (timeslice_id, feature_id) VALUES (52, 1);

-- Delete the old row - now there should be no more conflict
DELETE FROM derived_timeslice WHERE timeslice_id = 51;

-- Problem doesn't occur without an UPDATE statement
UPDATE derived_timeslice SET name = 'Updated' WHERE timeslice_id = 52;

-- This confirms there is only 1 row - no conflict
SELECT * FROM derived_timeslice;

COMMIT; -- Enforce constraint - error occurs here

The cause of the problem seems to be that the UPDATE performs a HOT update
of the new tuple, leaving in this case a dead tuple at (0,2) that is HOT
updated by (0,3). When unique_key_recheck() is invoked for (0,2), it
believes, correctly, that it has to perform the recheck anyway ... but it
tells check_exclusion_constraint that the check is being performed for
(0,2). So the index search inside check_exclusion_constraint finds the
live tuple at (0,3) and thinks that is a conflict.

This is reproducible clear back to 9.0 where exclusion constraints were
added.

The easiest fix seems to be to pass the HOT child's TID instead of the
TID we were called for. (Note that the other path, for a regular unique
constraint, is correct as-is because the original TID is what the index
will know about.)

The attached patch seems to fix the problem without breaking any existing
regression tests, but I wonder if anyone can see a hole in it.

regards, tom lane

Attachment Content-Type Size
exclusion-HOT-bug.patch text/x-diff 2.9 KB

In response to

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message Robert Haas 2015-05-11 02:05:24 Re: Re: BUG #12990: Missing pg_multixact/members files (appears to have wrapped, then truncated)
Previous Message David G. Johnston 2015-05-10 18:40:10 Re: BUG #13148: Unexpected deferred EXCLUDE constraint violation on derived table

Browse pgsql-hackers by date

  From Date Subject
Next Message Aliouii Ali 2015-05-10 21:10:05 default value dosen't get applyed in this situation
Previous Message Tom Lane 2015-05-10 18:42:28 Re: Custom/Foreign-Join-APIs (Re: [v9.5] Custom Plan API)