Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a6c0a5b

Browse files
committed
Don't throw serialization errors for self-conflicts in INSERT ON CONFLICT.
A transaction that conflicts against itself, for example INSERT INTO t(pk) VALUES (1),(1) ON CONFLICT DO NOTHING; should behave the same regardless of isolation level. It certainly shouldn't throw a serialization error, as retrying will not help. We got this wrong due to the ON CONFLICT logic not considering the case, as reported by Jason Dusek. Core of this patch is by Peter Geoghegan (based on an earlier patch by Thomas Munro), though I didn't take his proposed code refactoring for fear that it might have unexpected side-effects. Test cases by Thomas Munro and myself. Report: <CAO3NbwOycQjt2Oqy2VW-eLTq2M5uGMyHnGm=RNga4mjqcYD7gQ@mail.gmail.com> Related-Discussion: <57EE93C8.8080504@postgrespro.ru>
1 parent 6292c23 commit a6c0a5b

File tree

6 files changed

+218
-2
lines changed

6 files changed

+218
-2
lines changed

src/backend/executor/nodeModifyTable.c

+11-2
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,18 @@ ExecCheckHeapTupleVisible(EState *estate,
195195
return;
196196

197197
if (!HeapTupleSatisfiesVisibility(tuple, estate->es_snapshot, buffer))
198-
ereport(ERROR,
199-
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
198+
{
199+
/*
200+
* We should not raise a serialization failure if the conflict is
201+
* against a tuple inserted by our own transaction, even if it's not
202+
* visible to our snapshot. (This would happen, for example, if
203+
* conflicting keys are proposed for insertion in a single command.)
204+
*/
205+
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
206+
ereport(ERROR,
207+
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
200208
errmsg("could not serialize access due to concurrent update")));
209+
}
201210
}
202211

203212
/*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
Parsed test spec with 2 sessions
2+
3+
starting permutation: beginrr1 beginrr2 donothing1 c1 donothing2 c2 show
4+
step beginrr1: BEGIN ISOLATION LEVEL REPEATABLE READ;
5+
step beginrr2: BEGIN ISOLATION LEVEL REPEATABLE READ;
6+
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
7+
step c1: COMMIT;
8+
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
9+
step c2: COMMIT;
10+
step show: SELECT * FROM ints;
11+
key val
12+
13+
1 donothing1
14+
15+
starting permutation: beginrr1 beginrr2 donothing2 c2 donothing1 c1 show
16+
step beginrr1: BEGIN ISOLATION LEVEL REPEATABLE READ;
17+
step beginrr2: BEGIN ISOLATION LEVEL REPEATABLE READ;
18+
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
19+
step c2: COMMIT;
20+
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
21+
step c1: COMMIT;
22+
step show: SELECT * FROM ints;
23+
key val
24+
25+
1 donothing2
26+
27+
starting permutation: beginrr1 beginrr2 donothing1 donothing2 c1 c2 show
28+
step beginrr1: BEGIN ISOLATION LEVEL REPEATABLE READ;
29+
step beginrr2: BEGIN ISOLATION LEVEL REPEATABLE READ;
30+
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
31+
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING; <waiting ...>
32+
step c1: COMMIT;
33+
step donothing2: <... completed>
34+
error in steps c1 donothing2: ERROR: could not serialize access due to concurrent update
35+
step c2: COMMIT;
36+
step show: SELECT * FROM ints;
37+
key val
38+
39+
1 donothing1
40+
41+
starting permutation: beginrr1 beginrr2 donothing2 donothing1 c2 c1 show
42+
step beginrr1: BEGIN ISOLATION LEVEL REPEATABLE READ;
43+
step beginrr2: BEGIN ISOLATION LEVEL REPEATABLE READ;
44+
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
45+
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING; <waiting ...>
46+
step c2: COMMIT;
47+
step donothing1: <... completed>
48+
error in steps c2 donothing1: ERROR: could not serialize access due to concurrent update
49+
step c1: COMMIT;
50+
step show: SELECT * FROM ints;
51+
key val
52+
53+
1 donothing2
54+
55+
starting permutation: begins1 begins2 donothing1 c1 donothing2 c2 show
56+
step begins1: BEGIN ISOLATION LEVEL SERIALIZABLE;
57+
step begins2: BEGIN ISOLATION LEVEL SERIALIZABLE;
58+
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
59+
step c1: COMMIT;
60+
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
61+
step c2: COMMIT;
62+
step show: SELECT * FROM ints;
63+
key val
64+
65+
1 donothing1
66+
67+
starting permutation: begins1 begins2 donothing2 c2 donothing1 c1 show
68+
step begins1: BEGIN ISOLATION LEVEL SERIALIZABLE;
69+
step begins2: BEGIN ISOLATION LEVEL SERIALIZABLE;
70+
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
71+
step c2: COMMIT;
72+
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
73+
step c1: COMMIT;
74+
step show: SELECT * FROM ints;
75+
key val
76+
77+
1 donothing2
78+
79+
starting permutation: begins1 begins2 donothing1 donothing2 c1 c2 show
80+
step begins1: BEGIN ISOLATION LEVEL SERIALIZABLE;
81+
step begins2: BEGIN ISOLATION LEVEL SERIALIZABLE;
82+
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
83+
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING; <waiting ...>
84+
step c1: COMMIT;
85+
step donothing2: <... completed>
86+
error in steps c1 donothing2: ERROR: could not serialize access due to concurrent update
87+
step c2: COMMIT;
88+
step show: SELECT * FROM ints;
89+
key val
90+
91+
1 donothing1
92+
93+
starting permutation: begins1 begins2 donothing2 donothing1 c2 c1 show
94+
step begins1: BEGIN ISOLATION LEVEL SERIALIZABLE;
95+
step begins2: BEGIN ISOLATION LEVEL SERIALIZABLE;
96+
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING;
97+
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING; <waiting ...>
98+
step c2: COMMIT;
99+
step donothing1: <... completed>
100+
error in steps c2 donothing1: ERROR: could not serialize access due to concurrent update
101+
step c1: COMMIT;
102+
step show: SELECT * FROM ints;
103+
key val
104+
105+
1 donothing2

src/test/isolation/isolation_schedule

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ test: eval-plan-qual
2525
test: lock-update-delete
2626
test: lock-update-traversal
2727
test: insert-conflict-do-nothing
28+
test: insert-conflict-do-nothing-2
2829
test: insert-conflict-do-update
2930
test: insert-conflict-do-update-2
3031
test: insert-conflict-do-update-3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# INSERT...ON CONFLICT DO NOTHING test with multiple rows
2+
# in higher isolation levels
3+
4+
setup
5+
{
6+
CREATE TABLE ints (key int primary key, val text);
7+
}
8+
9+
teardown
10+
{
11+
DROP TABLE ints;
12+
}
13+
14+
session "s1"
15+
step "beginrr1" { BEGIN ISOLATION LEVEL REPEATABLE READ; }
16+
step "begins1" { BEGIN ISOLATION LEVEL SERIALIZABLE; }
17+
step "donothing1" { INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING; }
18+
step "c1" { COMMIT; }
19+
step "show" { SELECT * FROM ints; }
20+
21+
session "s2"
22+
step "beginrr2" { BEGIN ISOLATION LEVEL REPEATABLE READ; }
23+
step "begins2" { BEGIN ISOLATION LEVEL SERIALIZABLE; }
24+
step "donothing2" { INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING; }
25+
step "c2" { COMMIT; }
26+
27+
permutation "beginrr1" "beginrr2" "donothing1" "c1" "donothing2" "c2" "show"
28+
permutation "beginrr1" "beginrr2" "donothing2" "c2" "donothing1" "c1" "show"
29+
permutation "beginrr1" "beginrr2" "donothing1" "donothing2" "c1" "c2" "show"
30+
permutation "beginrr1" "beginrr2" "donothing2" "donothing1" "c2" "c1" "show"
31+
permutation "begins1" "begins2" "donothing1" "c1" "donothing2" "c2" "show"
32+
permutation "begins1" "begins2" "donothing2" "c2" "donothing1" "c1" "show"
33+
permutation "begins1" "begins2" "donothing1" "donothing2" "c1" "c2" "show"
34+
permutation "begins1" "begins2" "donothing2" "donothing1" "c2" "c1" "show"

src/test/regress/expected/insert_conflict.out

+35
Original file line numberDiff line numberDiff line change
@@ -727,3 +727,38 @@ select * from twoconstraints;
727727
(1 row)
728728

729729
drop table twoconstraints;
730+
-- check handling of self-conflicts at various isolation levels
731+
create table selfconflict (f1 int primary key, f2 int);
732+
begin transaction isolation level read committed;
733+
insert into selfconflict values (1,1), (1,2) on conflict do nothing;
734+
commit;
735+
begin transaction isolation level repeatable read;
736+
insert into selfconflict values (2,1), (2,2) on conflict do nothing;
737+
commit;
738+
begin transaction isolation level serializable;
739+
insert into selfconflict values (3,1), (3,2) on conflict do nothing;
740+
commit;
741+
begin transaction isolation level read committed;
742+
insert into selfconflict values (4,1), (4,2) on conflict(f1) do update set f2 = 0;
743+
ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time
744+
HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
745+
commit;
746+
begin transaction isolation level repeatable read;
747+
insert into selfconflict values (5,1), (5,2) on conflict(f1) do update set f2 = 0;
748+
ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time
749+
HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
750+
commit;
751+
begin transaction isolation level serializable;
752+
insert into selfconflict values (6,1), (6,2) on conflict(f1) do update set f2 = 0;
753+
ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time
754+
HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
755+
commit;
756+
select * from selfconflict;
757+
f1 | f2
758+
----+----
759+
1 | 1
760+
2 | 1
761+
3 | 1
762+
(3 rows)
763+
764+
drop table selfconflict;

src/test/regress/sql/insert_conflict.sql

+32
Original file line numberDiff line numberDiff line change
@@ -421,3 +421,35 @@ insert into twoconstraints values(2, '((0,0),(1,2))')
421421
on conflict on constraint twoconstraints_f2_excl do nothing; -- do nothing
422422
select * from twoconstraints;
423423
drop table twoconstraints;
424+
425+
-- check handling of self-conflicts at various isolation levels
426+
427+
create table selfconflict (f1 int primary key, f2 int);
428+
429+
begin transaction isolation level read committed;
430+
insert into selfconflict values (1,1), (1,2) on conflict do nothing;
431+
commit;
432+
433+
begin transaction isolation level repeatable read;
434+
insert into selfconflict values (2,1), (2,2) on conflict do nothing;
435+
commit;
436+
437+
begin transaction isolation level serializable;
438+
insert into selfconflict values (3,1), (3,2) on conflict do nothing;
439+
commit;
440+
441+
begin transaction isolation level read committed;
442+
insert into selfconflict values (4,1), (4,2) on conflict(f1) do update set f2 = 0;
443+
commit;
444+
445+
begin transaction isolation level repeatable read;
446+
insert into selfconflict values (5,1), (5,2) on conflict(f1) do update set f2 = 0;
447+
commit;
448+
449+
begin transaction isolation level serializable;
450+
insert into selfconflict values (6,1), (6,2) on conflict(f1) do update set f2 = 0;
451+
commit;
452+
453+
select * from selfconflict;
454+
455+
drop table selfconflict;

0 commit comments

Comments
 (0)