Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitfcff8a5

Browse files
committed
Detect SSI conflicts before reporting constraint violations
While prior to this patch the user-visible effect on the databaseof any set of successfully committed serializable transactions wasalways consistent with some one-at-a-time order of execution ofthose transactions, the presence of declarative constraints couldallow errors to occur which were not possible in any such ordering,and developers had no good workarounds to prevent user-facingerrors where they were not necessary or desired. This patch addsa check for serialization failure ahead of duplicate key checkingso that if a developer explicitly (redundantly) checks for thepre-existing value they will get the desired serialization failurewhere the problem is caused by a concurrent serializabletransaction; otherwise they will get a duplicate key error.While it would be better if the reads performed by the constraintscould count as part of the work of the transaction forserialization failure checking, and we will hopefully get theresome day, this patch allows a clean and reliable way for developersto work around the issue. In many cases existing code will alreadybe doing the right thing for this to "just work".Author: Thomas Munro, with minor editing of docs by meReviewed-by: Marko Tiikkaja, Kevin Grittner
1 parentbb14050 commitfcff8a5

File tree

11 files changed

+307
-7
lines changed

11 files changed

+307
-7
lines changed

‎doc/src/sgml/mvcc.sgml

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ ERROR: could not serialize access due to read/write dependencies among transact
644644
first. In <productname>PostgreSQL</productname> these locks do not
645645
cause any blocking and therefore can <emphasis>not</> play any part in
646646
causing a deadlock. They are used to identify and flag dependencies
647-
among concurrentserializable transactions which in certain combinations
647+
among concurrentSerializable transactions which in certain combinations
648648
can lead to serialization anomalies. In contrast, a Read Committed or
649649
Repeatable Read transaction which wants to ensure data consistency may
650650
need to take out a lock on an entire table, which could block other
@@ -679,12 +679,13 @@ ERROR: could not serialize access due to read/write dependencies among transact
679679

680680
<para>
681681
Consistent use of Serializable transactions can simplify development.
682-
The guarantee that any set of concurrent serializable transactions will
683-
have the same effect as if they were run one at a time means that if
684-
you can demonstrate that a single transaction, as written, will do the
685-
right thing when run by itself, you can have confidence that it will
686-
do the right thing in any mix of serializable transactions, even without
687-
any information about what those other transactions might do. It is
682+
The guarantee that any set of successfully committed concurrent
683+
Serializable transactions will have the same effect as if they were run
684+
one at a time means that if you can demonstrate that a single transaction,
685+
as written, will do the right thing when run by itself, you can have
686+
confidence that it will do the right thing in any mix of Serializable
687+
transactions, even without any information about what those other
688+
transactions might do, or it will not successfully commit. It is
688689
important that an environment which uses this technique have a
689690
generalized way of handling serialization failures (which always return
690691
with a SQLSTATE value of '40001'), because it will be very hard to
@@ -698,6 +699,26 @@ ERROR: could not serialize access due to read/write dependencies among transact
698699
for some environments.
699700
</para>
700701

702+
<para>
703+
While <productname>PostgreSQL</>'s Serializable transaction isolation
704+
level only allows concurrent transactions to commit if it can prove there
705+
is a serial order of execution that would produce the same effect, it
706+
doesn't always prevent errors from being raised that would not occur in
707+
true serial execution. In particular, it is possible to see unique
708+
constraint violations caused by conflicts with overlapping Serializable
709+
transactions even after explicitly checking that the key isn't present
710+
before attempting to insert it. This can be avoided by making sure
711+
that <emphasis>all</> Serializable transactions that insert potentially
712+
conflicting keys explicitly check if they can do so first. For example,
713+
imagine an application that asks the user for a new key and then checks
714+
that it doesn't exist already by trying to select it first, or generates
715+
a new key by selecting the maximum existing key and adding one. If some
716+
Serializable transactions insert new keys directly without following this
717+
protocol, unique constraints violations might be reported even in cases
718+
where they could not occur in a serial execution of the concurrent
719+
transactions.
720+
</para>
721+
701722
<para>
702723
For optimal performance when relying on Serializable transactions for
703724
concurrency control, these issues should be considered:

‎src/backend/access/nbtree/nbtinsert.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,14 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
391391
break;
392392
}
393393

394+
/*
395+
* Check for a conflict-in as we would if we were going to
396+
* write to this page. We aren't actually going to write,
397+
* but we want a chance to report SSI conflicts that would
398+
* otherwise be masked by this unique constraint violation.
399+
*/
400+
CheckForSerializableConflictIn(rel,NULL,buf);
401+
394402
/*
395403
* This is a definite conflict. Break the tuple down into
396404
* datums and report the error. But first, make sure we
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Parsed test spec with 2 sessions
2+
3+
starting permutation: r1 r2 w1 w2 c1 c2
4+
step r1: SELECT * FROM test WHERE i = 42;
5+
i
6+
7+
step r2: SELECT * FROM test WHERE i = 42;
8+
i
9+
10+
step w1: INSERT INTO test VALUES (42);
11+
step w2: INSERT INTO test VALUES (42); <waiting ...>
12+
step c1: COMMIT;
13+
step w2: <... completed>
14+
error in steps c1 w2: ERROR: could not serialize access due to read/write dependencies among transactions
15+
step c2: COMMIT;
16+
17+
starting permutation: r1 w1 c1 r2 w2 c2
18+
step r1: SELECT * FROM test WHERE i = 42;
19+
i
20+
21+
step w1: INSERT INTO test VALUES (42);
22+
step c1: COMMIT;
23+
step r2: SELECT * FROM test WHERE i = 42;
24+
i
25+
26+
42
27+
step w2: INSERT INTO test VALUES (42);
28+
ERROR: duplicate key value violates unique constraint "test_pkey"
29+
step c2: COMMIT;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Parsed test spec with 2 sessions
2+
3+
starting permutation: rw1 rw2 c1 c2
4+
step rw1: SELECT insert_unique(1, '1');
5+
insert_unique
6+
7+
8+
step rw2: SELECT insert_unique(1, '2'); <waiting ...>
9+
step c1: COMMIT;
10+
step rw2: <... completed>
11+
error in steps c1 rw2: ERROR: could not serialize access due to read/write dependencies among transactions
12+
step c2: COMMIT;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
Parsed test spec with 2 sessions
2+
3+
starting permutation: r1 r2 w1 w2 c1 c2
4+
step r1: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
5+
coalesce
6+
7+
3
8+
step r2: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
9+
coalesce
10+
11+
3
12+
step w1: INSERT INTO invoice VALUES (2016, 3);
13+
step w2: INSERT INTO invoice VALUES (2016, 3); <waiting ...>
14+
step c1: COMMIT;
15+
step w2: <... completed>
16+
error in steps c1 w2: ERROR: could not serialize access due to read/write dependencies among transactions
17+
step c2: COMMIT;
18+
19+
starting permutation: r1 w1 w2 c1 c2
20+
step r1: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
21+
coalesce
22+
23+
3
24+
step w1: INSERT INTO invoice VALUES (2016, 3);
25+
step w2: INSERT INTO invoice VALUES (2016, 3); <waiting ...>
26+
step c1: COMMIT;
27+
step w2: <... completed>
28+
error in steps c1 w2: ERROR: duplicate key value violates unique constraint "invoice_pkey"
29+
step c2: COMMIT;
30+
31+
starting permutation: r2 w1 w2 c1 c2
32+
step r2: SELECT COALESCE(MAX(invoice_number) + 1, 1) FROM invoice WHERE year = 2016;
33+
coalesce
34+
35+
3
36+
step w1: INSERT INTO invoice VALUES (2016, 3);
37+
step w2: INSERT INTO invoice VALUES (2016, 3); <waiting ...>
38+
step c1: COMMIT;
39+
step w2: <... completed>
40+
error in steps c1 w2: ERROR: duplicate key value violates unique constraint "invoice_pkey"
41+
step c2: COMMIT;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Parsed test spec with 2 sessions
2+
3+
starting permutation: r1 r2 w1 w2 c1 c2
4+
step r1: SELECT * FROM test;
5+
i
6+
7+
step r2: SELECT * FROM test;
8+
i
9+
10+
step w1: INSERT INTO test VALUES (42);
11+
step w2: INSERT INTO test VALUES (42); <waiting ...>
12+
step c1: COMMIT;
13+
step w2: <... completed>
14+
error in steps c1 w2: ERROR: could not serialize access due to read/write dependencies among transactions
15+
step c2: COMMIT;
16+
17+
starting permutation: r1 w1 c1 r2 w2 c2
18+
step r1: SELECT * FROM test;
19+
i
20+
21+
step w1: INSERT INTO test VALUES (42);
22+
step c1: COMMIT;
23+
step r2: SELECT * FROM test;
24+
i
25+
26+
42
27+
step w2: INSERT INTO test VALUES (42);
28+
ERROR: duplicate key value violates unique constraint "test_pkey"
29+
step c2: COMMIT;

‎src/test/isolation/isolation_schedule

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
test: read-write-unique
2+
test: read-write-unique-2
3+
test: read-write-unique-3
4+
test: read-write-unique-4
15
test: simple-write-skew
26
test: receipt-report
37
test: temporal-range-integrity
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Read-write-unique test.
2+
3+
setup
4+
{
5+
CREATETABLEtest(iintegerPRIMARYKEY);
6+
}
7+
8+
teardown
9+
{
10+
DROPTABLEtest;
11+
}
12+
13+
session"s1"
14+
setup{ BEGINISOLATIONLEVELSERIALIZABLE;}
15+
step"r1"{SELECT *FROMtestWHEREi=42;}
16+
step"w1"{INSERTINTOtestVALUES(42);}
17+
step"c1"{COMMIT;}
18+
19+
session"s2"
20+
setup{ BEGINISOLATIONLEVELSERIALIZABLE;}
21+
step"r2"{SELECT *FROMtestWHEREi=42;}
22+
step"w2"{INSERTINTOtestVALUES(42);}
23+
step"c2"{COMMIT;}
24+
25+
# Two SSI transactions see that there is no row with value 42
26+
# in the table, then try to insert that value; T1 inserts,
27+
# and then T2 blocks waiting for T1 to commit. Finally,
28+
# T2 reports a serialization failure.
29+
30+
permutation"r1""r2""w1""w2""c1""c2"
31+
32+
# If the value is already visible before T2 begins, then a
33+
# regular unique constraint violation should still be raised
34+
# by T2.
35+
36+
permutation"r1""w1""c1""r2""w2""c2"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Read-write-unique test.
2+
# From bug report 9301.
3+
4+
setup
5+
{
6+
CREATETABLEtest (
7+
keyintegerUNIQUE,
8+
valtext
9+
);
10+
11+
CREATEORREPLACEFUNCTIONinsert_unique(kinteger,vtext)RETURNSvoid
12+
LANGUAGESQLAS $$
13+
INSERTINTOtest (key,val)SELECTk,vWHERENOTEXISTS (SELECTkeyFROMtestWHEREkey=k);
14+
$$;
15+
}
16+
17+
teardown
18+
{
19+
DROPFUNCTIONinsert_unique(integer,text);
20+
DROPTABLEtest;
21+
}
22+
23+
session"s1"
24+
setup {BEGINISOLATIONLEVELSERIALIZABLE; }
25+
step"rw1" {SELECTinsert_unique(1,'1'); }
26+
step"c1" {COMMIT; }
27+
28+
session"s2"
29+
setup {BEGINISOLATIONLEVELSERIALIZABLE; }
30+
step"rw2" {SELECTinsert_unique(1,'2'); }
31+
step"c2" {COMMIT; }
32+
33+
permutation"rw1""rw2""c1""c2"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Read-write-unique test.
2+
# Implementing a gapless sequence of ID numbers for each year.
3+
4+
setup
5+
{
6+
CREATETABLEinvoice (
7+
yearint,
8+
invoice_numberint,
9+
PRIMARYKEY (year,invoice_number)
10+
);
11+
12+
INSERTINTOinvoiceVALUES (2016,1), (2016,2);
13+
}
14+
15+
teardown
16+
{
17+
DROPTABLEinvoice;
18+
}
19+
20+
session"s1"
21+
setup {BEGINISOLATIONLEVELSERIALIZABLE; }
22+
step"r1" {SELECTCOALESCE(MAX(invoice_number)+1,1)FROMinvoiceWHEREyear=2016; }
23+
step"w1" {INSERTINTOinvoiceVALUES (2016,3); }
24+
step"c1" {COMMIT; }
25+
26+
session"s2"
27+
setup {BEGINISOLATIONLEVELSERIALIZABLE; }
28+
step"r2" {SELECTCOALESCE(MAX(invoice_number)+1,1)FROMinvoiceWHEREyear=2016; }
29+
step"w2" {INSERTINTOinvoiceVALUES (2016,3); }
30+
step"c2" {COMMIT; }
31+
32+
# if they both read first then there should be an SSI conflict
33+
permutation"r1""r2""w1""w2""c1""c2"
34+
35+
# cases where one session doesn't explicitly read before writing:
36+
37+
# if s2 doesn't explicitly read, then trying to insert the value
38+
# generates a unique constraint violation after s1 commits, as if s2
39+
# ran after s1
40+
permutation"r1""w1""w2""c1""c2"
41+
42+
# if s1 doesn't explicitly read, but s2 does, then s1 inserts and
43+
# commits first, should s2 experience an SSI failure instead of a
44+
# unique constraint violation? there is no serial order of operations
45+
# (s1, s2) or (s2, s1) where s1 succeeds, and s2 doesn't see the row
46+
# in an explicit select but then fails to insert due to unique
47+
# constraint violation
48+
permutation"r2""w1""w2""c1""c2"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Read-write-unique test.
2+
3+
setup
4+
{
5+
CREATETABLEtest(iintegerPRIMARYKEY);
6+
}
7+
8+
teardown
9+
{
10+
DROPTABLEtest;
11+
}
12+
13+
session"s1"
14+
setup{ BEGINISOLATIONLEVELSERIALIZABLE;}
15+
step"r1"{SELECT *FROMtest;}
16+
step"w1"{INSERTINTOtestVALUES(42);}
17+
step"c1"{COMMIT;}
18+
19+
session"s2"
20+
setup{ BEGINISOLATIONLEVELSERIALIZABLE;}
21+
step"r2"{SELECT *FROMtest;}
22+
step"w2"{INSERTINTOtestVALUES(42);}
23+
step"c2"{COMMIT;}
24+
25+
# Two SSI transactions see that there is no row with value 42
26+
# in the table, then try to insert that value; T1 inserts,
27+
# and then T2 blocks waiting for T1 to commit. Finally,
28+
# T2 reports a serialization failure.
29+
#
30+
# (In an earlier version of Postgres, T2 would report a unique
31+
# constraint violation).
32+
33+
permutation"r1""r2""w1""w2""c1""c2"
34+
35+
# If the value is already visible before T2 begins, then a
36+
# regular unique constraint violation should still be raised
37+
# by T2.
38+
39+
permutation"r1""w1""c1""r2""w2""c2"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp