Re: MERGE vs REPLACE

From: Bruce Momjian <pgman(at)candle(dot)pha(dot)pa(dot)us>
To: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Cc: Dennis Bjorklund <db(at)zigo(dot)dhs(dot)org>, Zeugswetter Andreas DCP SD <ZeugswetterA(at)spardat(dot)at>, Csaba Nagy <nagy(at)ecircle-ag(dot)com>, Martijn van Oosterhout <kleptog(at)svana(dot)org>, Dann Corbit <DCorbit(at)connx(dot)com>, Simon Riggs <simon(at)2ndquadrant(dot)com>, Rick Gigger <rick(at)alpinenetworking(dot)com>, Christopher Kings-Lynne <chriskl(at)familyhealth(dot)com(dot)au>, "Jim C(dot) Nasby" <jnasby(at)pervasive(dot)com>, josh(at)agliodbs(dot)com, pgsql-hackers(at)postgresql(dot)org, Jaime Casanova <systemguards(at)gmail(dot)com>, Peter Eisentraut <peter_e(at)gmx(dot)net>
Subject: Re: MERGE vs REPLACE
Date: 2005-11-20 17:26:18
Message-ID: 200511201726.jAKHQI007897@candle.pha.pa.us
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Tom Lane wrote:
> Bruce Momjian <pgman(at)candle(dot)pha(dot)pa(dot)us> writes:
> > Oh, good point. I was thinking just about concurrent MERGEs. However,
> > it is more complicated than that. By definitaion you can not see
> > changes from other transactions while your statement is being run (even
> > if you increment CommandCounter), so to be atomic, you would still see
> > the row even though some other transaction had deleted it.
>
> We would have to use the same semantics we use now for read-committed
> UPDATE, that is look at the latest version of the row even though this
> would not normally be visible to the transaction's snapshot.
>
> In the case of a serializable transaction, no doubt we should fail if
> any concurrent change actually happens.

I have some psaudocode to explain what we want for this feature,
whatever syntax we choose:

Start
Check unique index
Found
lock row for update
if zero rows, return to start
if more than one row, fail
update row
Notfound
create savepoint
insert row into heap
lock index page
if conflicting index entry, abort savepoint, return to start
add index entry
unlock index page

While the "notfound" case might look strange, we actually use this exact
method for inserts now, see ExecInsert() and _bt_doinsert().
Particularly see this comment in the second function:

/*
* If we're not allowing duplicates, make sure the key isn't already in
* the index.
*
* NOTE: obviously,_bt_check_unique can only detect keys that are already in
* the index; so it cannot defend against concurrent insertions of the
* same key. We protect against that by means of holding a write lock on
* the target page. Any other would-be inserter of the same key must
* acquire a write lock on the same target page, so only one would-be
* inserter can be making the check at one time. Furthermore, once we are
* past the check we hold write locks continuously until we have performed
* our insertion, so no later inserter can fail to see our insertion.
* (This requires some care in _bt_insertonpg.)
*
* If we must wait for another xact, we release the lock while waiting, and
* then must start over completely.
*/

Here is the unique check error from _bt_check_unique():

ereport(ERROR,
(errcode(ERRCODE_UNIQUE_VIOLATION),
errmsg("duplicate key violates unique constraint \"%s\"",
RelationGetRelationName(rel))));

I think the problem here is that it is going to longjump() back to
postgres.c (and out of your code loop). While we have savepoints, I
think they only work coming from client applications, rather than inside
our code. Ideally you would like to be able to say:

savepoint();
func();
rollback_to_savepoint();

but you can't, so I think you are going to have to factor out that
unique error callback and return a failure code to the caller. I
suppose some boolean flag need to be added to _bt_doinsert(), but that
is called via a function pointer for the index type, so you are going to
have to update the insert function signatures for all access methods.
The good news is that only btree supports unique indexes, according to
the documentation ("Only B-tree currently supports unique indexes") so
for the other access methods the extra parameter is just ignored.

Another issue is multiple unique indexes. What if the first unique
index matches one row, but a different row matches the second unique
indexed column? Fail because unique checks do not identify exactly one
row?

Or the _new_ value for the second indexed column conflicts with the
second unique index. The MERGE/REPLACE should fail. The UPDATE block
will handle this on its own, but the INSERT block will need to check for
that an really error out, rather than return to the caller, so the loop
in ExecInsertIndexTuples() has to restart on unique failure _only_ on
the first index check, not the subsequent ones.

One simplification would be to allow MERGE/REPLACE only on a table that
has a single unique index.

--
Bruce Momjian | http://candle.pha.pa.us
pgman(at)candle(dot)pha(dot)pa(dot)us | (610) 359-1001
+ If your life is a hard drive, | 13 Roberts Road
+ Christ can be your backup. | Newtown Square, Pennsylvania 19073

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Bruce Momjian 2005-11-20 17:43:42 Re: Numeric 508 datatype
Previous Message pgsql 2005-11-20 17:21:02 unsubscribe