Prev: [HACKERS] Row-level Locks & SERIALIZABLE transactions, postgres vs. Oracle
Next: [HACKERS] HS/SR Assert server crash
From: Florian Pflug on 14 May 2010 05:46 On May 14, 2010, at 2:37 , Greg Stark wrote: > On Thu, May 13, 2010 at 10:25 PM, Florian Pflug <fgp(a)phlo.org> wrote: >> C1: BEGIN >> C1: SELECT * FROM t WHERE id = 1 FOR UPDATE >> C2: BEGIN >> C2: SET TRANSACTION ISOLATION LEVEL SERIALIZABLE >> C2: SELECT * FROM t -- Take snapshot before C1 commits >> C1: COMMIT >> C2: DELETE FROM t WHERE id = 1 >> C2: COMMIT >> > > Can you give an actual realistic example -- ie, not doing a select for > update and then never updating the row or with an explanation of what > the programmer is attempting to accomplish with such an unusual > sequence? The rest of the post talks about FKs but I don't see any > here... The table "t" is supposed to represent the parent table of a FK constraint. The SELECT FOR UPDATE is done upon an INSERT to the child table to protect the parent row against concurrent deletion. I've used FOR UPDATE instead of FOR SHARE because I did test this against oracle also, and oracle does not support FOR SHARE. Here's a full example of a pair of FK triggers in PL/PGSQL that work correctly in READ COMMITTED mode but fail to enforce the constraint in SERIALIZABLE mode as the following sequence of commands show. With my proposal, the DELETE would again raise a serialization error and hence keep the constraint satisfied. C1: BEGIN C1: INSERT INTO child (parent_id) VALUES (1) -- Locks the parent row FOR UPDATE C2: BEGIN C2: SET TRANSACTION ISOLATION LEVEL SERIALIZABLE C2: SELECT TRUE -- Take snapshot *before* C1 commits C1: COMMIT C2: DELETE FROM parent WHERE parent_id = 1 -- Succeeds C2: COMMIT ---------- CREATE TABLE parent (parent_id SERIAL NOT NULL PRIMARY KEY); CREATE TABLE child (child_id SERIAL NOT NULL PRIMARY KEY, parent_id INTEGER NOT NULL); CREATE FUNCTION ri_parent() RETURNS TRIGGER AS $body$ BEGIN PERFORM TRUE FROM child WHERE parent_id = OLD.parent_id; IF FOUND THEN RAISE SQLSTATE '23503' USING MESSAGE = 'Parent ' || OLD.parent_id || ' still referenced during ' || TG_OP; END IF; RETURN NULL; END; $body$ LANGUAGE PLPGSQL VOLATILE; CREATE TRIGGER ri_parent AFTER UPDATE OR DELETE ON parent FOR EACH ROW EXECUTE PROCEDURE ri_parent(); CREATE FUNCTION ri_child() RETURNS TRIGGER AS $body$ BEGIN PERFORM TRUE FROM parent WHERE parent_id = NEW.parent_id FOR UPDATE OF parent; IF NOT FOUND THEN RAISE SQLSTATE '23503' USING MESSAGE = 'Parent ' || NEW.parent_id || ' does not exist during ' || TG_OP; END IF; RETURN NULL; END; $body$ LANGUAGE PLPGSQL VOLATILE; CREATE TRIGGER ri_child AFTER INSERT OR UPDATE ON child FOR EACH ROW EXECUTE PROCEDURE ri_child(); ---------- best regards, Florian Pflug -- Sent via pgsql-hackers mailing list (pgsql-hackers(a)postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
From: Robert Haas on 14 May 2010 16:54 On Thu, May 13, 2010 at 5:39 PM, Tom Lane <tgl(a)sss.pgh.pa.us> wrote: > Florian Pflug <fgp(a)phlo.org> writes: >> All in all, I believe that SHARE and UPDATE row-level locks should be >> changed to cause concurrent UPDATEs to fail with a serialization >> error. > > I don't see an argument for doing that for FOR SHARE locks, and it > already happens for FOR UPDATE (at least if the row actually gets > updated). AFAICS this proposal mainly breaks things, in pursuit of > an unnecessary and probably-impossible-anyway goal of making FK locking > work with only user-level snapshots. After giving this considerable thought and testing the behavior at some length, I think the OP has it right. One thing I sometimes need to do is denormalize a copy of a field, e.g. CREATE TABLE parent (id serial, mode integer not null, primary key (id)); CREATE TABLE child (id serial, parent_id integer not null references parent (id), parent_mode integer not null); The way I have typically implemented this in the past is: 1. Add a trigger to the parent table so that, whenever the mode column gets updated, we do an update on the parent_mode of all children. 2. Add a trigger to the child table so that, when a new child is inserted, it initializes parent_mode from its parent. I do SELECT with FOR UPDATE on the parent parent can't change under me; though FOR SHARE ought to be enough also since we're just trying to lock out concurrent updates. Suppose T1 updates the parent's mode while T2 adds a new child; then both commit. In read committed mode, this seems to work OK regardless of the order of T1 and T2. If T1 grabs the lock first, then T2 sees the updated version of the row after T1 commits. If T2 grabs the lock first, then the update on the parent blocks until the child commits. Subsequently, when the trigger fires, it apparently uses an up-to-date snapshot, so the new child is updated also. In serializable mode, things are not so good. If T1 grabs the lock first, the child waits to see whether it commits or aborts. On commit, it complains that it can't serialize and aborts, which is reasonable - transaction aborts are the price you pay for serializability. If T2 grabs the lock first, the update on the parent blocks as before, but now the update is done with the old snapshot and ignores the new child, so the new child now has a value for parent_mode that doesn't match the parent's actual mode. That is, you get the wrong answer due to a serialization anomaly that didn't existed at the read committed level. Increasing the transaction isolation level is supposed to *eliminate* serialization anomalies, not create them. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise Postgres Company -- Sent via pgsql-hackers mailing list (pgsql-hackers(a)postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
From: Rob Wultsch on 15 May 2010 03:22 On Fri, May 14, 2010 at 7:32 AM, Kevin Grittner <Kevin.Grittner(a)wicourts.gov> wrote: > Oracle, and all other MVCC databases I've read about outside of PostgreSQL, use > an "update in place with a rollback log" technique. Have you looked at PBXT (which is explicitly NOT SERIALIZABLE)? -- Rob Wultsch wultsch(a)gmail.com -- Sent via pgsql-hackers mailing list (pgsql-hackers(a)postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
From: Rob Wultsch on 15 May 2010 09:21 On Sat, May 15, 2010 at 4:09 AM, Kevin Grittner <Kevin.Grittner(a)wicourts.gov> wrote: > Anything in particular you wanted me to notice about it besides that? Nope. It was just a counter point to your previous comment. -- Rob Wultsch wultsch(a)gmail.com -- Sent via pgsql-hackers mailing list (pgsql-hackers(a)postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
From: Robert Haas on 16 May 2010 21:30 On Sun, May 16, 2010 at 9:07 PM, Florian Pflug <fgp(a)phlo.org> wrote: > On May 14, 2010, at 22:54 , Robert Haas wrote: >> On Thu, May 13, 2010 at 5:39 PM, Tom Lane <tgl(a)sss.pgh.pa.us> wrote: >>> Florian Pflug <fgp(a)phlo.org> writes: >>>> All in all, I believe that SHARE and UPDATE row-level locks should be >>>> changed to cause concurrent UPDATEs to fail with a serialization >>>> error. >>> >>> I don't see an argument for doing that for FOR SHARE locks, and it >>> already happens for FOR UPDATE (at least if the row actually gets >>> updated). AFAICS this proposal mainly breaks things, in pursuit of >>> an unnecessary and probably-impossible-anyway goal of making FK locking >>> work with only user-level snapshots. >> >> After giving this considerable thought and testing the behavior at >> some length, I think the OP has it right. One thing I sometimes need >> to do is denormalize a copy of a field, e.g. >> >> <snipped example> > > I've whipped up a quick and still rather dirty patch that implements the behavior I proposed, at least for the case of conflicts between FOR UPDATE locks and updates. With the patch, any attempt to UPDATE or FOR UPDATE lock a row that has concurrently been FOR UPDATE locked will cause a serialization error. (The same for an actually updated row of course, but that happened before too). > > While this part of the patch was fairly straight forward, make FOR SHARE conflict too seems to be much harder. The assumption that a lock becomes irrelevant after the transaction(s) that held it completely is built deeply into the multi xact machinery that powers SHARE locks. That machinery therefore assumes that once all members of a multi xact have completed the multi xact is dead also. But my proposal depends on a SERIALIZABLE transaction being able to find if any of the lockers of a row are invisible under it's snapshot - for which it'd need any multi xact containing invisible xids to outlive its snapshot. Thanks for putting this together. I suggest adding it to the open CommitFest - even if we decide to go forward with this, I don't imagine anyone is going to be excited about changing it during beta. https://commitfest.postgresql.org/action/commitfest_view/open -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise Postgres Company -- Sent via pgsql-hackers mailing list (pgsql-hackers(a)postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
First
|
Prev
|
Next
|
Last
Pages: 1 2 3 Prev: [HACKERS] Row-level Locks & SERIALIZABLE transactions, postgres vs. Oracle Next: [HACKERS] HS/SR Assert server crash |