Re: SELECT FOR UPDATE differs inside and outside a pl/pgsql

From: Mark Shewmaker <mark(at)primefactor(dot)com>
To: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Cc: pgsql-bugs(at)postgresql(dot)org
Subject: Re: SELECT FOR UPDATE differs inside and outside a pl/pgsql
Date: 2003-12-18 09:28:35
Message-ID: 1071739715.2083.481.camel@k9
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

On Wed, 2003-12-17 at 19:57, Tom Lane wrote:
> Mark Shewmaker <mark(at)primefactor(dot)com> writes:
> > If a "FOR UPDATE executes before LIMIT" rule stopped the function
> > from ever locking a row, it's still curious why didn't it stop the
> > direct command from ever locking a row as well.
>
> I think it would. Did you try the test the other way around (with the
> direct command being blocked behind someone who deletes the first row)?

Yes, or at least I've done the test that I think you're asking about.
(See Session_A-with-early-delete occurring with Session B_1 below.)

To be as clear as possible as to the actual tests run:

+-----------------+---------------------------------------------------+
|Run this in psql:| Along with one of these in a second psql session: |
+-----------------+-----------------+-----------------+---------------+
| Session A | Session B_1 | Session B_2 | Session B_3 |
+-----------------+-----------------+-----------------+---------------+
| | | | |
| # begin | | | |
| transaction | | | |
| | | | |
| | # begin | # begin | # begin |
| | transaction; | transaction | transaction |
| | | | |
| # select * from | | | |
| mytable limit | | | |
| 1 for update; | | | |
| (returns a=1) | | | |
| | | | |
|(NOTE: if the | | | |
|following delete | | | |
|statement is | | | |
|moved here,before| | | |
|session B_?'s | | | |
|selects, it has | | | |
|no effect on | | | |
|session B_?'s | | | |
|hangs & results) | | | |
| | | | |
| | # select * from | # select | # select |
| | mytable limit | myfunction(); | myfunction2();
| | 1 for update; | (hangs) | (hangs) |
| | (hangs) | (hangs) | (hangs) |
| # delete from | (hangs) | (hangs) | (hangs) |
| mytable where | (hangs) | (hangs) | (hangs) |
| a=1; | (hangs) | (hangs) | (hangs) |
| (succeeds with | (hangs) | (hangs) | (hangs) |
| "DELETE 1") | (hangs) | (hangs) | (hangs) |
| | (hangs) | (hangs) | (hangs) |
| # commit; | (hangs) | (hangs) | (hangs) |
| (succeeds with | (hangs) | (hangs) | (hangs) |
| "COMMIT") | (hangs) | (hangs) | (hangs) |
| | (returns with | (hangs) | (returns with |
| | no rows) | (hangs) | a=2) |
| | | (hangs) | |
| | # select * from | (hangs) | # delete from |
| | mytable limit | (hangs) | mytable |
| | 1 for update; | (hangs) | where a=2; |
| | (returns with | (hangs) | (succeeds with|
| | a=2) | (hangs) | "DELETE 1") |
| | | (hangs) | |
| | # delete from | (hangs) | # commit; |
| | mytable where | (hangs) | (succeeds with|
| | a=2; | (hangs) | "COMMIT") |
| | (succeeds with | (hangs) | |
| | "DELETE 1") | (hangs) | |
| | | (hangs) | |
| | # commit; | (hangs) | |
| | (succeeds with | (hangs) | |
| | "COMMIT") | (hangs) | |
| | | (hangs) | |
+-----------------+-----------------+-----------------+---------------+
| Before each test: # delete from mytable; |
| # insert into mytable (a) values (1); |
| # insert into mytable (a) values (2); |
| # insert into mytable (a) values (3); |
+---------------------------------------------------------------------+
| myfunction() mostly consists of: |
| |
| LOOP |
| select * into myrow from mytable limit 1 for update; |
| if found then exit; |
| end if; |
| END LOOP; |
| return myrow.a; |
+---------------------------------------------------------------------+
| myfunction2() mostly consists of: |
| |
| select * into myrow from mytable for update; |
| return myrow.a; |
+---------------------------------------------------------------------+

Does this contain the test case you were asking about?

("select * into myrow from mytable for update;" as you suggested, with
it's implicit limit done within the function is still a great solution
for me.)

> > 2. There's now a difference between the suggested "select * into
> > myrow from mytable for update;" run within a function, with its
> > JUST GREAT behavior, and the original "select * from mytable
> > limit 1 for update;":
>
> Yeah, because plpgsql is implicitly letting you apply a LIMIT 1 after
> the FOR UPDATE, whereas there is no way to get the equivalent behavior
> in plain SQL.

Ahhh. Okay.

Now that you've explained that one a second time, it's finally begun to
sink in. :-)

(That doesn't explain the original difference discussed above, of
course.)

> > 4. As an aside, since another way to directly solve the problem would
> > be a way to only select rows that aren't locked, are there any
> > thoughts on having that sort of functionality in a future revision?
>
> There have been previous discussions about how to do this sort of
> select-the-first-available-job logic, and IIRC people have found
> suitable implementations. Search the archives. It's not on-topic
> for pgsql-bugs, in any case.

Thanks.

I'll search the archives further, and bring up it up in a more suitable
list if need be and where it will be more on topic.

--
Mark Shewmaker
mark(at)primefactor(dot)com

In response to

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message Russell Garrett 2003-12-18 11:51:05 Urgent: Key constraints behaving weirdly
Previous Message PostgreSQL Bugs List 2003-12-18 07:30:44 BUG #1013: Authentication doesn't work