BUG #4925: "select ... for update" doesn't affect rows from sub-query

From: "Steve Caligo" <steve(dot)caligo(at)ctie(dot)etat(dot)lu>
To: pgsql-bugs(at)postgresql(dot)org
Subject: BUG #4925: "select ... for update" doesn't affect rows from sub-query
Date: 2009-07-16 16:34:13
Message-ID: 200907161634.n6GGYDQJ049925@wwwmaster.postgresql.org
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-bugs


The following bug has been logged online:

Bug reference: 4925
Logged by: Steve Caligo
Email address: steve(dot)caligo(at)ctie(dot)etat(dot)lu
PostgreSQL version: 8.3.7 and 8.4.0
Operating system: Archlinux and Gentoo 8.3.7, Gentoo 8.4.0
Description: "select ... for update" doesn't affect rows from
sub-query
Details:

While trying to guarantee data consistency when doing concurrent processing,
I stumbled upon your cautions mentionned in your documentation (especially
"limit" ... "for update"):
http://www.postgresql.org/docs/8.4/static/sql-select.html

I tried working around this limitation and the statement on the same page
seemed promising to me:
"If FOR UPDATE or FOR SHARE is applied to a view or sub-query, it affects
all tables used in the view or sub-query."

But unfortunately the latter statement doesn't seem to be true and the
subquery isn't protected by row locks, as the following examples show.

1) INITIAL SITUATION

Create a simple table with some data. No constraints, no indexes, just the
bare minimum:

create table test (
id integer,
name varchar(10),
c integer
);

insert into test values
(1, 'test1', 0),
(2, 'test2', 0),
(3, 'test3', 0),
(4, 'test4', 0),
(5, 'test5', 0)
;

2) FIRST TRY, USING "UPDATE WHERE ID = ( SELECT )"
The goal is to have two clients set their unique ID to a single/different
row from the table. First, using "limit" in a slightly different way:

1=> begin transaction;
2=> begin transaction;

1=> update test set c = 1 where id = (
select id from test where c = 0 order by id limit 1
)
; -- updates row id=1
2=> update test set c = 2 where id = (
select id from test where c = 0 order by id limit 1
)
; -- forced to wait on lock

1=> commit; -- client #2 continues
1=> select * from test where id = 1;
id | name | c
----+-------+---
1 | test1 | 1
(1 row)

2=> commit;
2=> select * from test where id = 1;
id | name | c
----+-------+---
1 | test1 | 2
(1 row)

1=> select * from test where id = 1;
id | name | c
----+-------+---
1 | test1 | 2
(1 row)

Conclusion: didn't work.
Probably because the subquery is executed before the update and not affected
by row locking.

Expected behaviour: one client update one row to c=1 and the other client
updates a different row to c=2.

3) SECOND TRY, PROTECTING THE UPDATE BY AN ADDITIONAL "SELECT ... FOR
UPDATE" AND AVOIDING THE "LIMIT"

1=> begin transaction;
2=> begin transaction;
1=> select id from test where id = (
select min(id) from test where c = 0
) for update;
id
----
2
(1 row)

2=> select id from test where id = (
select min(id) from test where c = 0
) for update; -- forced to wait on lock

1=> update test set c = 1 where id = 2;
1=> commit; -- client #2 continues:

2=> -- client #2 outputs:
id
----
2
(1 row)

2=> select * from test where id = 2;
id | name | c
----+-------+---
2 | test2 | 1
(1 row)
2=> -- now this isn't what we initially asked for, let's just repeat the
query once more:

2=> select id from test where id = ( select min(id) from test where c = 0 )
for update;
id
----
3
(1 row)

Conclusion: didn't work.
The situation one ends up in is one that contradicts your above statement,
but also seemingly violates the "I" in ACID. In 3), client #2 is clearly
affected by the actions of client #1. While serialized transactions or full
table locks would avoid this race condition, it either requires large
changes in the application or impacts performance during contention.

Adding an additional "c = 0" to the main query of 3) of course suppresses
the row from the concurrent update, but it shouldn't have been returned with
a value of "c = 1" because the transaction #2 started prior to the update
statement of #1.

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message Juan C. Aragon 2009-07-16 18:50:52 FATAL: could not reattach to shared memory (key=268, addr=01E30000): 487
Previous Message Frank van Vugt 2009-07-16 15:28:54 Re: bug or simply not enough stack space?