Ok, Now it's more clear for me. Thanks to all, especially on @Kevin deep explanation and (tx3) example. Question is closed.
I intuited that it might be as all of You explained, but was not sure, I was confused by "predicate lock" - I thought it's related to `SELECT+WHERE` and not to data (a kind of "subject lock"). Now I understood that key-words are "serial execution in any order will lead to conflict" - here I also was a little bit confused by chronological order of commit. P.S. One more "offtop" question - What kind of frameworks do automatically retries for failed transactions? Are Hibernate/Spring in that list? Best Regards, AlexL On Thu, Mar 10, 2016 at 12:41 AM, Kevin Grittner <kgri...@gmail.com> wrote: > On Wed, Mar 9, 2016 at 1:39 PM, Alexandru Lazarev > <alexandru.laza...@gmail.com> wrote: > > Jeff's answer is entirely correct; I'm just going to go into more > detail -- just in case you're interested enough to work through it. > > > `CREATE TABLE public.foo (id serial PRIMARY KEY, mynum integer);` > > > > and following data > > > > id | mynum > > ----+------- > > 1 | 10 > > 2 | 10 > > 3 | 10 > > 4 | 10 > > (4 rows) > > > > I run 2 serialize transactions in parallel (2 `psql` consoles): > > > > -- both transactions > > mydb=# begin; > > BEGIN > > mydb=# set transaction isolation level serializable; > > SET > > > > -- tx1 > > mydb=# select * from foo where mynum < 100; > > id | mynum > > ----+------- > > 1 | 10 > > 2 | 10 > > 3 | 10 > > 4 | 10 > > (4 rows) > > --tx1: Shouldn't freeze data visible for tx1 select? > > Yes, tx1 does have a snapshot which will guarantee that it sees a > repeatable set view of the data for this predicate. > > > --tx2 > > mydb=# insert into foo (mynum) values (10); > > INSERT 0 1 > > -- tx2 will insert next row with id 5 in foo table > > -- Shouldn't insert of tx2 broke data snapshot visible for tx1? > > The snapshot tx1 has guarantees that overlapping changes won't > change it's view of things, and there is no reason for anything to > be blocked or canceled here. The insert creates what is called a > read-write dependency (or rw-conflict for short) that establishes > that in any serial ordering of a set of transactions which includes > tx1 and tx2, tx1 must precede tx2 in the apparent order of > execution. > > > --tx1 > > mydb=# update foo set mynum = 20 where id < 100; > > UPDATE 4 > > -- Shouldn't here appear serialization fail or at least on tx1 > commit? > > No, there is no cycle in the apparent order of execution. The > snapshot for tx1 still limits it to the same set of rows, and there > is nothing visible that is inconsistent with tx1 running before > tx2. > > > --tx2 > > mydb=# commit; > > COMMIT > > > > --tx1 > > mydb=# commit; > > COMMIT > > -- tx1 Commit is OK - no any error > > According to the SQL standard, and in the PostgreSQL implementation > of SERIALIZABLE transactions, commit order does not, by itself, > establish apparent order of execution. > > > -- implicit tx > > mydb=# select * from foo; > > id | mynum > > ----+------- > > 1 | 20 > > 2 | 20 > > 3 | 20 > > 4 | 20 > > 5 | 10 > > (5 rows) > > As Jeff said, this is consistent with the implicit transaction > running last, so tx1 -> tx2 -> implicit_tx. > > Now, you are pretty close to a situation which does need to trigger > a serialization failure -- just switch the commit of tx1 and the > implicit transaction. If tx2 has committed but tx1 has not yet > committed: > > mydb=# select * from foo; > id | mynum > ----+------- > 1 | 10 > 2 | 10 > 3 | 10 > 4 | 10 > 5 | 10 > (5 rows) > > *Now* we have a problem -- this only makes sense if the implicit tx > was run after tx2 and before tx1. So apparent order of execution > is tx1 -> tx2 -> implicit_tx -> tx1. There is a cycle in the > apparent order of execution, which causes anomalies which can ruin > data integrity. Now, if the implicit transaction is not > serializable, it is allowed to see such things, but if you make it > serializable (and let's call it tx3 now) it sees a state where only > tx2 ran; tx1 could not have run: > > -- tx3 > mydb=# begin; > BEGIN > mydb=# set transaction isolation level serializable; > SET > mydb=# select * from foo; > id | mynum > ----+------- > 1 | 10 > 2 | 10 > 3 | 10 > 4 | 10 > 5 | 10 > (5 rows) > > mydb=# commit; > COMMIT > > So now, tx1 is not allowed to commit, or for that matter do > anything else -- it has been "doomed" by tx3: > > mydb=# select * from foo; > ERROR: could not serialize access due to read/write dependencies > among transactions > DETAIL: Reason code: Canceled on identification as a pivot, during > conflict out checking. > HINT: The transaction might succeed if retried. > > Hopefully you are using some framework to automatically detect this > SQLSTATE and retry the transaction from the start. So on retry, > tx1 does this: > > -- tx1 (retry) > mydb=# begin; > BEGIN > mydb=# set transaction isolation level serializable; > SET > mydb=# select * from foo where mynum < 100; > id | mynum > ----+------- > 1 | 10 > 2 | 10 > 3 | 10 > 4 | 10 > 5 | 10 > (5 rows) > > mydb=# update foo set mynum = 20 where id < 100; > UPDATE 5 > mydb=# select * from foo; > id | mynum > ----+------- > 1 | 20 > 2 | 20 > 3 | 20 > 4 | 20 > 5 | 20 > (5 rows) > > mydb=# commit; > COMMIT > > Now the result of all successfully committed serializiable > transactions is consistent with the order tx2 -> tx3 -> tx1. All > is good. > > Kevin Grittner >