Thread

Commits

  1. Force NO SCROLL for plpgsql's implicit cursors.

  2. Avoid misbehavior when persisting a non-stable cursor.

  1. BUG #17050: cursor with for update + commit in loop

    The Post Office <noreply@postgresql.org> — 2021-06-08T07:49:25Z

    The following bug has been logged on the website:
    
    Bug reference:      17050
    Logged by:          Алексей Булгаков
    Email address:      bulgakovalexey1980@gmail.com
    PostgreSQL version: 12.7
    Operating system:   Red Hat 4.4.7-23
    Description:        
    
    create table public.test_tuple_stream (
    	   id serial,
    	   nm text,
    	   dt timestamptz,
    	   num bigint
    	   );
    
    CREATE OR REPLACE PROCEDURE public.test_tuple_stream()
     LANGUAGE plpgsql
    AS $procedure$
      declare
    	  l_cur cursor for
    	    select id
    	    from public.test_tuple_stream
    	    order by id
    	    for update;
    	begin
    	  for rec in l_cur loop
    	    update public.test_tuple_stream
    	    set num = num + 1
    	    where id = rec.id;
    	   
    	    commit;	   
    	  end loop;
    	 
    	  commit;
    	END;
    $proc	  
    	  
    --	truncate table public.test_tuple_stream;  
    	  
    	insert into public.test_tuple_stream(nm, dt, num)
    	values ('A', now(), 1);
    	insert into public.test_tuple_stream(nm, dt, num)
    	values ('B', now(), 1);
    	insert into public.test_tuple_stream(nm, dt, num)
    	values ('C', now(), 1);
    
      call public.test_tuple_stream()
    
    	select *
    	from public.test_tuple_stream
    	order by id
    
    If run procedure test_tuple_stream then in result updated 2 rows of 3.
    Why?
    if remove in procedure "for update" or "commit in loop" then updated 3 rows
    of 3
    
    
  2. Re: BUG #17050: cursor with for update + commit in loop

    Tom Lane <tgl@sss.pgh.pa.us> — 2021-06-08T18:54:15Z

    PG Bug reporting form <noreply@postgresql.org> writes:
    > If run procedure test_tuple_stream then in result updated 2 rows of 3.
    > Why?
    
    So what's happening here is that at the point of the first COMMIT,
    we need to save the output of the cursor query, because we have to
    release locks and so forth.  The expectation is that subsequent
    fetches will read from a tuplestore rather than the live query.
    
    To do that, PersistHoldablePortal supposes that it can rewind and
    re-read the query's executor run.  But that only works if the
    query is guaranteed stable, which anything involving FOR UPDATE
    is not.  In the example at hand, when we re-read the row with ID 1,
    nodeLockRows sees that its state is now TM_SelfModified thanks to the
    UPDATE we just did.  This causes it to not return that row, so that
    what ends up in the tuplestore is only the rows with IDs 2 and 3.
    But since the cursor "knows" that one row has already been read out,
    the next fetch will return the row with ID 3, and the procedure's
    loop never visits the row with ID 2.
    
    I imagine that similar misbehavior could be constructed around
    queries containing volatile functions (e.g. random()), rather than
    FOR UPDATE.
    
    The only obvious way to fix this is to always save aside the output
    of a cursor query in case we need to persist it later, so that
    PersistHoldablePortal doesn't have to assume that rewinding is safe.
    That would be pretty catastrophic for performance, though, so I doubt
    anybody will be happy with that answer.
    
    For cursors that aren't marked scrollable, we might be able to say
    that we only save the *rest* of the query output, and then adjust
    the cursor state appropriately for that choice.  Seems possibly
    nontrivial though, and there's still the question of what to do
    for scrollable ones.
    
    Anyway, many thanks for the report!  But don't hold your breath
    for a fix; this is going to take some thought.
    
    			regards, tom lane
    
    
    
    
  3. Re: BUG #17050: cursor with for update + commit in loop

    Tom Lane <tgl@sss.pgh.pa.us> — 2021-06-08T20:01:46Z

    I wrote:
    > The only obvious way to fix this is to always save aside the output
    > of a cursor query in case we need to persist it later, so that
    > PersistHoldablePortal doesn't have to assume that rewinding is safe.
    > That would be pretty catastrophic for performance, though, so I doubt
    > anybody will be happy with that answer.
    > For cursors that aren't marked scrollable, we might be able to say
    > that we only save the *rest* of the query output, and then adjust
    > the cursor state appropriately for that choice.  Seems possibly
    > nontrivial though, and there's still the question of what to do
    > for scrollable ones.
    
    Actually ... maybe it's not that bad.  The nonscrollable case seems
    to be quite simple to fix, and as for the scrollable case, maybe
    we can just say it's on the user's head that the query produce
    stable results.  There's already a prohibition on using FOR UPDATE
    with SCROLL, and the DECLARE CURSOR reference page has some warnings
    about volatile queries with WITH HOLD, which is basically the same
    case as we're worried about here.
    
    I think the DECLARE CURSOR page needs some modernization to mention
    that cursors in procedures are basically the same as WITH HOLD.
    But as far as code changes go, the attached seems sufficient.
    
    			regards, tom lane