mirror of
https://github.com/postgres/postgres.git
synced 2025-10-18 04:29:09 +03:00
Avoid serializability errors when locking a tuple with a committed update
When key-share locking a tuple that has been not-key-updated, and the update is a committed transaction, in some cases we raised serializability errors: ERROR: could not serialize access due to concurrent update Because the key-share doesn't conflict with the update, the error is unnecessary and inconsistent with the case that the update hasn't committed yet. This causes problems for some usage patterns, even if it can be claimed that it's sufficient to retry the aborted transaction: given a steady stream of updating transactions and a long locking transaction, the long transaction can be starved indefinitely despite multiple retries. To fix, we recognize that HeapTupleSatisfiesUpdate can return HeapTupleUpdated when an updating transaction has committed, and that we need to deal with that case exactly as if it were a non-committed update: verify whether the two operations conflict, and if not, carry on normally. If they do conflict, however, there is a difference: in the HeapTupleBeingUpdated case we can just sleep until the concurrent transaction is gone, while in the HeapTupleUpdated case this is not possible and we must raise an error instead. Per trouble report from Olivier Dony. In addition to a couple of test cases that verify the changed behavior, I added a test case to verify the behavior that remains unchanged, namely that errors are raised when a update that modifies the key is used. That must still generate serializability errors. One pre-existing test case changes behavior; per discussion, the new behavior is actually the desired one. Discussion: https://www.postgresql.org/message-id/560AA479.4080807@odoo.com https://www.postgresql.org/message-id/20151014164844.3019.25750@wrigleys.postgresql.org Backpatch to 9.3, where the problem appeared.
This commit is contained in:
66
src/test/isolation/specs/lock-committed-keyupdate.spec
Normal file
66
src/test/isolation/specs/lock-committed-keyupdate.spec
Normal file
@@ -0,0 +1,66 @@
|
||||
# Test locking of a tuple with a committed key-update. In this case,
|
||||
# the update conflicts with the lock, so failures are expected, except
|
||||
# in READ COMMITTED isolation mode.
|
||||
#
|
||||
# Some of the permutations are commented out that work fine in the
|
||||
# lock-committed-update test, because in this case the update blocks.
|
||||
|
||||
setup
|
||||
{
|
||||
DROP TABLE IF EXISTS lcku_table;
|
||||
CREATE TABLE lcku_table (id INTEGER PRIMARY KEY, value TEXT);
|
||||
INSERT INTO lcku_table VALUES (1, 'one');
|
||||
INSERT INTO lcku_table VALUES (3, 'two');
|
||||
}
|
||||
|
||||
teardown
|
||||
{
|
||||
DROP TABLE lcku_table;
|
||||
}
|
||||
|
||||
session "s1"
|
||||
step "s1b" { BEGIN; }
|
||||
step "s1l" { SELECT pg_advisory_lock(578902068); }
|
||||
step "s1u" { UPDATE lcku_table SET id = 2 WHERE id = 3; }
|
||||
step "s1hint" { SELECT * FROM lcku_table; }
|
||||
step "s1ul" { SELECT pg_advisory_unlock(578902068); }
|
||||
step "s1c" { COMMIT; }
|
||||
teardown { SELECT pg_advisory_unlock_all(); }
|
||||
|
||||
session "s2"
|
||||
step "s2b1" { BEGIN ISOLATION LEVEL READ COMMITTED; }
|
||||
step "s2b2" { BEGIN ISOLATION LEVEL REPEATABLE READ; }
|
||||
step "s2b3" { BEGIN ISOLATION LEVEL SERIALIZABLE; }
|
||||
step "s2l" { SELECT * FROM lcku_table WHERE pg_advisory_lock(578902068) IS NOT NULL FOR KEY SHARE; }
|
||||
step "s2c" { COMMIT; }
|
||||
teardown { SELECT pg_advisory_unlock_all(); }
|
||||
|
||||
permutation "s1b" "s2b1" "s1l" "s2l" "s1u" "s1c" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b1" "s1l" "s1u" "s2l" "s1c" "s1ul" "s2c"
|
||||
#permutation "s1b" "s2b1" "s1l" "s2l" "s1ul" "s1u" "s1c" "s2c"
|
||||
permutation "s1b" "s2b1" "s1l" "s1u" "s1ul" "s2l" "s1c" "s2c"
|
||||
|
||||
permutation "s1b" "s2b1" "s1l" "s2l" "s1u" "s1c" "s1hint" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b1" "s1l" "s1u" "s2l" "s1c" "s1hint" "s1ul" "s2c"
|
||||
#permutation "s1b" "s2b1" "s1l" "s2l" "s1ul" "s1u" "s1c" "s1hint" "s2c"
|
||||
permutation "s1b" "s2b1" "s1l" "s1u" "s1ul" "s2l" "s1c" "s1hint" "s2c"
|
||||
|
||||
permutation "s1b" "s2b2" "s1l" "s2l" "s1u" "s1c" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b2" "s1l" "s1u" "s2l" "s1c" "s1ul" "s2c"
|
||||
#permutation "s1b" "s2b2" "s1l" "s2l" "s1ul" "s1u" "s1c" "s2c"
|
||||
permutation "s1b" "s2b2" "s1l" "s1u" "s1ul" "s2l" "s1c" "s2c"
|
||||
|
||||
permutation "s1b" "s2b2" "s1l" "s2l" "s1u" "s1c" "s1hint" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b2" "s1l" "s1u" "s2l" "s1c" "s1hint" "s1ul" "s2c"
|
||||
#permutation "s1b" "s2b2" "s1l" "s2l" "s1ul" "s1u" "s1c" "s1hint" "s2c"
|
||||
permutation "s1b" "s2b2" "s1l" "s1u" "s1ul" "s2l" "s1c" "s1hint" "s2c"
|
||||
|
||||
permutation "s1b" "s2b3" "s1l" "s2l" "s1u" "s1c" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b3" "s1l" "s1u" "s2l" "s1c" "s1ul" "s2c"
|
||||
#permutation "s1b" "s2b3" "s1l" "s2l" "s1ul" "s1u" "s1c" "s2c"
|
||||
permutation "s1b" "s2b3" "s1l" "s1u" "s1ul" "s2l" "s1c" "s2c"
|
||||
|
||||
permutation "s1b" "s2b3" "s1l" "s2l" "s1u" "s1c" "s1hint" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b3" "s1l" "s1u" "s2l" "s1c" "s1hint" "s1ul" "s2c"
|
||||
#permutation "s1b" "s2b3" "s1l" "s2l" "s1ul" "s1u" "s1c" "s1hint" "s2c"
|
||||
permutation "s1b" "s2b3" "s1l" "s1u" "s1ul" "s2l" "s1c" "s1hint" "s2c"
|
62
src/test/isolation/specs/lock-committed-update.spec
Normal file
62
src/test/isolation/specs/lock-committed-update.spec
Normal file
@@ -0,0 +1,62 @@
|
||||
# Test locking of a tuple with a committed update. When the lock does not
|
||||
# conflict with the update, no blocking and no serializability errors should
|
||||
# occur.
|
||||
|
||||
setup
|
||||
{
|
||||
DROP TABLE IF EXISTS lcu_table;
|
||||
CREATE TABLE lcu_table (id INTEGER PRIMARY KEY, value TEXT);
|
||||
INSERT INTO lcu_table VALUES (1, 'one');
|
||||
}
|
||||
|
||||
teardown
|
||||
{
|
||||
DROP TABLE lcu_table;
|
||||
}
|
||||
|
||||
session "s1"
|
||||
step "s1b" { BEGIN; }
|
||||
step "s1l" { SELECT pg_advisory_lock(380170116); }
|
||||
step "s1u" { UPDATE lcu_table SET value = 'two' WHERE id = 1; }
|
||||
step "s1hint" { SELECT * FROM lcu_table; }
|
||||
step "s1ul" { SELECT pg_advisory_unlock(380170116); }
|
||||
step "s1c" { COMMIT; }
|
||||
teardown { SELECT pg_advisory_unlock_all(); }
|
||||
|
||||
session "s2"
|
||||
step "s2b1" { BEGIN ISOLATION LEVEL READ COMMITTED; }
|
||||
step "s2b2" { BEGIN ISOLATION LEVEL REPEATABLE READ; }
|
||||
step "s2b3" { BEGIN ISOLATION LEVEL SERIALIZABLE; }
|
||||
step "s2l" { SELECT * FROM lcu_table WHERE pg_advisory_lock(380170116) IS NOT NULL FOR KEY SHARE; }
|
||||
step "s2c" { COMMIT; }
|
||||
teardown { SELECT pg_advisory_unlock_all(); }
|
||||
|
||||
permutation "s1b" "s2b1" "s1l" "s2l" "s1u" "s1c" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b1" "s1l" "s1u" "s2l" "s1c" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b1" "s1l" "s2l" "s1ul" "s1u" "s1c" "s2c"
|
||||
permutation "s1b" "s2b1" "s1l" "s1u" "s1ul" "s2l" "s1c" "s2c"
|
||||
|
||||
permutation "s1b" "s2b1" "s1l" "s2l" "s1u" "s1c" "s1hint" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b1" "s1l" "s1u" "s2l" "s1c" "s1hint" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b1" "s1l" "s2l" "s1ul" "s1u" "s1c" "s1hint" "s2c"
|
||||
permutation "s1b" "s2b1" "s1l" "s1u" "s1ul" "s2l" "s1c" "s1hint" "s2c"
|
||||
|
||||
permutation "s1b" "s2b2" "s1l" "s2l" "s1u" "s1c" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b2" "s1l" "s1u" "s2l" "s1c" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b2" "s1l" "s2l" "s1ul" "s1u" "s1c" "s2c"
|
||||
permutation "s1b" "s2b2" "s1l" "s1u" "s1ul" "s2l" "s1c" "s2c"
|
||||
|
||||
permutation "s1b" "s2b2" "s1l" "s2l" "s1u" "s1c" "s1hint" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b2" "s1l" "s1u" "s2l" "s1c" "s1hint" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b2" "s1l" "s2l" "s1ul" "s1u" "s1c" "s1hint" "s2c"
|
||||
permutation "s1b" "s2b2" "s1l" "s1u" "s1ul" "s2l" "s1c" "s1hint" "s2c"
|
||||
|
||||
permutation "s1b" "s2b3" "s1l" "s2l" "s1u" "s1c" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b3" "s1l" "s1u" "s2l" "s1c" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b3" "s1l" "s2l" "s1ul" "s1u" "s1c" "s2c"
|
||||
permutation "s1b" "s2b3" "s1l" "s1u" "s1ul" "s2l" "s1c" "s2c"
|
||||
|
||||
permutation "s1b" "s2b3" "s1l" "s2l" "s1u" "s1c" "s1hint" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b3" "s1l" "s1u" "s2l" "s1c" "s1hint" "s1ul" "s2c"
|
||||
permutation "s1b" "s2b3" "s1l" "s2l" "s1ul" "s1u" "s1c" "s1hint" "s2c"
|
||||
permutation "s1b" "s2b3" "s1l" "s1u" "s1ul" "s2l" "s1c" "s1hint" "s2c"
|
38
src/test/isolation/specs/update-locked-tuple.spec
Normal file
38
src/test/isolation/specs/update-locked-tuple.spec
Normal file
@@ -0,0 +1,38 @@
|
||||
# Test updating a locked tuple. When the lock doesn't conflict with the
|
||||
# update, no blocking nor serializability problems should occur.
|
||||
|
||||
setup
|
||||
{
|
||||
DROP TABLE IF EXISTS users, orders;
|
||||
CREATE TABLE users (id INTEGER PRIMARY KEY,
|
||||
name varchar,
|
||||
sometime timestamp);
|
||||
CREATE TABLE orders (id INTEGER PRIMARY KEY,
|
||||
name varchar,
|
||||
user_id INTEGER REFERENCES users (id));
|
||||
INSERT INTO users (id, name) VALUES (1, 'olivier');
|
||||
INSERT INTO orders (id, name) VALUES (1, 'order of olivier (1)');
|
||||
}
|
||||
|
||||
teardown
|
||||
{
|
||||
DROP TABLE users, orders;
|
||||
}
|
||||
|
||||
session "s1"
|
||||
step "s1b" { BEGIN ISOLATION LEVEL REPEATABLE READ; }
|
||||
step "s1u1" { UPDATE orders SET name = 'order of olivier (2)', user_id = 1 WHERE id = 1; }
|
||||
step "s1u2" { UPDATE orders SET name = 'order of olivier (3)', user_id = 1 WHERE id = 1; }
|
||||
step "s1c" { COMMIT; }
|
||||
|
||||
session "s2"
|
||||
step "s2b" { BEGIN ISOLATION LEVEL REPEATABLE READ; }
|
||||
step "s2u" { UPDATE users SET sometime = '1830-10-04' WHERE id = 1; }
|
||||
step "s2c" { COMMIT; }
|
||||
|
||||
permutation "s1b" "s2b" "s2u" "s2c" "s1u1" "s1u2" "s1c"
|
||||
permutation "s1b" "s2b" "s2u" "s1u1" "s2c" "s1u2" "s1c"
|
||||
permutation "s1b" "s2b" "s1u1" "s2u" "s2c" "s1u2" "s1c"
|
||||
permutation "s1b" "s1u1" "s2b" "s2u" "s2c" "s1u2" "s1c"
|
||||
permutation "s1b" "s1u1" "s2b" "s1u2" "s2u" "s2c" "s1c"
|
||||
permutation "s1b" "s1u1" "s1u2" "s2b" "s2u" "s2c" "s1c"
|
Reference in New Issue
Block a user