Обсуждение: currval() race condition on server?

Поиск
Список
Период
Сортировка

currval() race condition on server?

От
Adriaan Joubert
Дата:
Hi,

    I've run into an intermittent problem with our code recently. We have
the following set-up:

table A : some data table
table B : a history table for table A

A trigger copies the old version of a row into table B whenever an
update is done on table A. Both A and B contain an audit number, and the
trigger obtains an audit number from a sequence and inserts it into the
row inserted into table A.

For some bookkeeping purposes I need the new audit number back from the
update, so I submit a prepared statement through jdbc of the form

UPDATE A SET ....; SELECT currval('ip_audit_seq');

On the first call I get

  ERROR: currval of sequence "ip_audit_seq" is not yet defined in this
session

Note that this works without any trouble if I issue the commands in a
transaction through psql, and this used to work in earlier versions of
postgres.

We have a lot of users on very high latency links, so it is important
for the responsiveness of the application to try to do the update and
select in a single database trip.

So I'm wondering whether there is some type of race condition, where the
selection of the new value from ip_audit_seq is not available to the
currval function straight away? Anybody got any ideas on this?

Cheers,

Adriaan

Re: currval() race condition on server?

От
Achilleas Mantzios
Дата:
Στις Δευτέρα 23 Οκτώβριος 2006 16:49, ο/η Adriaan Joubert έγραψε:
> Hi,
>
>     I've run into an intermittent problem with our code recently. We have
> the following set-up:
>
> table A : some data table
> table B : a history table for table A
>
> A trigger copies the old version of a row into table B whenever an
> update is done on table A. Both A and B contain an audit number, and the
> trigger obtains an audit number from a sequence and inserts it into the
> row inserted into table A.

What do you mean here? Inserts it into the row inserted....

>
> For some bookkeeping purposes I need the new audit number back from the
> update, so I submit a prepared statement through jdbc of the form
>
> UPDATE A SET ....; SELECT currval('ip_audit_seq');
>
> On the first call I get
>
>   ERROR: currval of sequence "ip_audit_seq" is not yet defined in this
> session

That normally means that no nextval has been called on the sequence during the
session in question.
Can you give us the trigger code?

>
> Note that this works without any trouble if I issue the commands in a
> transaction through psql, and this used to work in earlier versions of
> postgres.
>

In any case double check that the server version/JDBC version match.

> We have a lot of users on very high latency links, so it is important
> for the responsiveness of the application to try to do the update and
> select in a single database trip.
>
> So I'm wondering whether there is some type of race condition, where the
> selection of the new value from ip_audit_seq is not available to the
> currval function straight away? Anybody got any ideas on this?
>
> Cheers,
>
> Adriaan
>
> ---------------------------(end of broadcast)---------------------------
> TIP 4: Have you searched our list archives?
>
>                http://archives.postgresql.org

--
Achilleas Mantzios

Re: currval() race condition on server?

От
Tom Lane
Дата:
Adriaan Joubert <a.joubert@albourne.com> writes:
> For some bookkeeping purposes I need the new audit number back from the
> update, so I submit a prepared statement through jdbc of the form
>
> UPDATE A SET ....; SELECT currval('ip_audit_seq');

It's not possible to put two SQL commands into one prepared statement
--- at least not for the normal server-side meaning of "prepared statement".
I dunno what the JDBC driver is doing with this, but I wonder if it's
silently dropping the UPDATE part :-(

            regards, tom lane

Re: currval() race condition on server?

От
Dave Cramer
Дата:
On 23-Oct-06, at 9:49 AM, Adriaan Joubert wrote:

> Hi,
>
>     I've run into an intermittent problem with our code recently. We
> have the following set-up:
>
> table A : some data table
> table B : a history table for table A
>
> A trigger copies the old version of a row into table B whenever an
> update is done on table A. Both A and B contain an audit number,
> and the trigger obtains an audit number from a sequence and inserts
> it into the row inserted into table A.
>
> For some bookkeeping purposes I need the new audit number back from
> the update, so I submit a prepared statement through jdbc of the form
>
> UPDATE A SET ....; SELECT currval('ip_audit_seq');
>
> On the first call I get
>
>  ERROR: currval of sequence "ip_audit_seq" is not yet defined in
> this session
currval is only defined after you call nextval in that connection.
>
> Note that this works without any trouble if I issue the commands in
> a transaction through psql, and this used to work in earlier
> versions of postgres.
>
> We have a lot of users on very high latency links, so it is
> important for the responsiveness of the application to try to do
> the update and select in a single database trip.
>
> So I'm wondering whether there is some type of race condition,
> where the selection of the new value from ip_audit_seq is not
> available to the currval function straight away? Anybody got any
> ideas on this?

There is no race condition
>
> Cheers,
>
> Adriaan
>
> ---------------------------(end of
> broadcast)---------------------------
> TIP 4: Have you searched our list archives?
>
>               http://archives.postgresql.org
>


Re: currval() race condition on server?

От
Adriaan Joubert
Дата:
Dave Cramer wrote:
>
> On 23-Oct-06, at 9:49 AM, Adriaan Joubert wrote:
>
>> Hi,
>>
>>     I've run into an intermittent problem with our code recently. We
>> have the following set-up:
>>
>> table A : some data table
>> table B : a history table for table A
>>
>> A trigger copies the old version of a row into table B whenever an
>> update is done on table A. Both A and B contain an audit number, and
>> the trigger obtains an audit number from a sequence and inserts it
>> into the row inserted into table A.
>>
>> For some bookkeeping purposes I need the new audit number back from
>> the update, so I submit a prepared statement through jdbc of the form
>>
>> UPDATE A SET ....; SELECT currval('ip_audit_seq');
>>
>> On the first call I get
>>
>>  ERROR: currval of sequence "ip_audit_seq" is not yet defined in this
>> session
> currval is only defined after you call nextval in that connection.

Yes, and this is done in a C trigger that is called as a result of the
update (it is a BEFORE INSERT OR DELETE OR UPDATE ... trigger). The C
code does a

   /* Find a plan for getting the next sequence number */
   plan = find_plan(sequence_name, &ExecPlans, &nExecPlans);
   if (plan->splan == NULL) {
     sprintf(query, "SELECT nextval('%s')", sequence_name);
     /* Prepare plan for query */
     pplan = SPI_prepare(query, 0, NULL);
     if (pplan == NULL)
       elog(ERROR, "audit(%s): SPI_prepare returned %d",
       sequence_name, SPI_result);
     pplan = SPI_saveplan(pplan);
     if (pplan == NULL)
       elog(ERROR, "audit(%s): SPI_saveplan returned %d",
       sequence_name, SPI_result);
     plan->splan = pplan;
   }
   /* Execute the plan */
   ret = SPI_execp(plan->splan, NULL, NULL, 0);
   if (ret < 0)
     elog(ERROR, "audit(%s): SPI_execp returned %d", sequence_name, ret);
   /* Get the new sequence number */
   new_seq = (int) DatumGetInt64
     ( SPI_getbinval( SPI_tuptable->vals[0], SPI_tuptable->tupdesc,
             1, &isnull) );


The update part of the trigger then inserts the sequence number into the
row (new_tuple being the tuple that is passed into the trigger)

     newtuple = SPI_modifytuple(rel, newtuple, 1, &i_audit,
                   (Datum *) &new_seq, NULL);

which puts the sequence number into the relation. As this is in a BEFORE
trigger, I would have through that it should be available to currval
before executing the second statement in the query.


As to Tom's question: the jdbc driver is executing the update without
any problems. On the java side I can do an

    PreparedStatement st = connection.prepareStatement(...);
    ...
    st.execute();
    nUpdated = st.getUpdateCount();
          if (nUpdated == 1 && st.getMoreResults()) {
                 ResultSet rs = st.getResultSet();
                 if (rs.next()) {
                     oldAudit_ = audit_;
                     setAudit(rs.getInt(1));
                 }
         }
         st.close();

which works well. So the real question is why currval is not working
even though the update succeeds? Or rather - the update does not
succeed, as the transaction is rolled back, but if I try to do the same
thing a second time it does.

Perhaps the assumption that the update has succeeded is incorrect - but
then, if the update statement fails, it should never attempt to execute
the SELECT currval(), should it? Certainly in updates that fail we do
not get an error from the SELECT currval().

Thanks for all your responses!

Adriaan

Re: currval() race condition on server?

От
Oliver Jowett
Дата:
Tom Lane wrote:
> Adriaan Joubert <a.joubert@albourne.com> writes:
>
>>For some bookkeeping purposes I need the new audit number back from the
>>update, so I submit a prepared statement through jdbc of the form
>>
>>UPDATE A SET ....; SELECT currval('ip_audit_seq');
>
>
> It's not possible to put two SQL commands into one prepared statement
> --- at least not for the normal server-side meaning of "prepared statement".
> I dunno what the JDBC driver is doing with this, but I wonder if it's
> silently dropping the UPDATE part :-(

The driver should split this query on the semicolons and send
Parse/Bind/Execute for each part, followed by a Sync at the end. If it
ends up using named statements due to JDBC statement reuse, it should
use a different name for each part. Of course there might be unknown
bugs in there, but it has been designed to handle this sort of compound
statement..

To the OP: Is autocommit on or off? Can you reproduce the problem with a
URL parameter of loglevel=2 and see what it looks like? (that'll
generate a trace protocol-level messages)

-O

Re: currval() race condition on server?

От
Adriaan Joubert
Дата:
Oliver Jowett wrote:

> The driver should split this query on the semicolons and send
> Parse/Bind/Execute for each part, followed by a Sync at the end. If it
> ends up using named statements due to JDBC statement reuse, it should
> use a different name for each part. Of course there might be unknown
> bugs in there, but it has been designed to handle this sort of compound
> statement..
>
> To the OP: Is autocommit on or off? Can you reproduce the problem with a
> URL parameter of loglevel=2 and see what it looks like? (that'll
> generate a trace protocol-level messages)

autocommit is off.

I have not been able to reproduce this with logging on - even with
logging off the problem only occurs intermittently. Looking at the
output from the trace everything seems to happen as expected - see trace
below. Yet we are getting a few of these errors in our logs every day -
obviously only the first time we ask for the value of the sequence back.
Scary thought is that may be getting an older value back in other
instances. It feels an awful lot like a timing issue where the sequence
number is retrieved, but there is a delay until currval can use it. I'm
not sure how currval works.

I will continue to try to reproduce this with more logging, but would
appreciate any ideas.

Thanks!

Adriaan

<=BE CommandStatus(BEGIN)
  <=BE ParseComplete [null]
  <=BE BindComplete [null]
  <=BE RowDescription(1)
  <=BE DataRow
  <=BE CommandStatus(SELECT)
  <=BE ReadyForQuery(T)
simple execute,
handler=org.postgresql.jdbc2.AbstractJdbc2Statement$StatementResultHandler@87e9bf,
maxRows=0, fetchSize=0, flags=1
  FE=> Parse(stmt=null,query="UPDATE prm SET ... AND
_audit=$16",oids={23,23,701,23,701,23,23,0,701,701,701,0,0,23,23,23})
  FE=>

Bind(stmt=null,portal=null,$1=<421>,$2=<1>,$3=<0.21927195726630877>,$4=<18>,$5=<0.376345008550404>,$6=<2>,$7=<1>,$8=<NULL>,$9=<0.045037791453060014>,$10=<0.020180805785169714>,$11=<1.0>,$12=<NULL>,$13=<NULL>,$14=<387>,$15=<435>,$16=<3382993>)
  FE=> Describe(portal=null)
  FE=> Execute(portal=null,limit=0)
  FE=> Parse(stmt=null,query=" SELECT currval('ip_audit_seq')",oids={})
  FE=> Bind(stmt=null,portal=null)
  FE=> Describe(portal=null)
  FE=> Execute(portal=null,limit=0)
  FE=> Sync
  <=BE ParseComplete [null]
  <=BE BindComplete [null]
  <=BE NoData
  <=BE CommandStatus(UPDATE 1)
  <=BE ParseComplete [null]
  <=BE BindComplete [null]
  <=BE RowDescription(1)
  <=BE DataRow
  <=BE CommandStatus(SELECT)
  <=BE ReadyForQuery(T)
simple execute,
handler=org.postgresql.jdbc2.AbstractJdbc2Connection$TransactionCommandHandler@1033450,
maxRows=0, fetchSize=0, flags=22
  FE=> Bind(stmt=S_2,portal=null)
  FE=> Execute(portal=null,limit=1)
  FE=> Sync
  <=BE BindComplete [null]
  <=BE CommandStatus(COMMIT)
  <=BE ReadyForQuery(I)

Re: currval() race condition on server?

От
Dave Cramer
Дата:
On 24-Oct-06, at 3:37 AM, Adriaan Joubert wrote:

> Oliver Jowett wrote:
>
>> The driver should split this query on the semicolons and send
>> Parse/Bind/Execute for each part, followed by a Sync at the end.
>> If it ends up using named statements due to JDBC statement reuse,
>> it should use a different name for each part. Of course there
>> might be unknown bugs in there, but it has been designed to handle
>> this sort of compound statement..
>> To the OP: Is autocommit on or off? Can you reproduce the problem
>> with a URL parameter of loglevel=2 and see what it looks like?
>> (that'll generate a trace protocol-level messages)
>
> autocommit is off.
>
> I have not been able to reproduce this with logging on - even with
> logging off the problem only occurs intermittently. Looking at the
> output from the trace everything seems to happen as expected - see
> trace below. Yet we are getting a few of these errors in our logs
> every day - obviously only the first time we ask for the value of
> the sequence back. Scary thought is that may be getting an older
> value back in other instances. It feels an awful lot like a timing
> issue where the sequence number is retrieved, but there is a delay
> until currval can use it. I'm not sure how currval works.

currval works like this:

When you execute nextval('sequence_name') the result of that is
stored in your session. A session lives as long as this connection is
alive. If nextval('sequence_name') has not  been called in this
session then currval('sequence_name') is undefined.

Hopefully this helps

Dave
>
> I will continue to try to reproduce this with more logging, but
> would appreciate any ideas.
>
> Thanks!
>
> Adriaan
>
> <=BE CommandStatus(BEGIN)
>  <=BE ParseComplete [null]
>  <=BE BindComplete [null]
>  <=BE RowDescription(1)
>  <=BE DataRow
>  <=BE CommandStatus(SELECT)
>  <=BE ReadyForQuery(T)
> simple execute, handler=org.postgresql.jdbc2.AbstractJdbc2Statement
> $StatementResultHandler@87e9bf, maxRows=0, fetchSize=0, flags=1
>  FE=> Parse(stmt=null,query="UPDATE prm SET ... AND _audit=
> $16",oids={23,23,701,23,701,23,23,0,701,701,701,0,0,23,23,23})
>  FE=> Bind(stmt=null,portal=null,$1=<421>,$2=<1>,
> $3=<0.21927195726630877>,$4=<18>,$5=<0.376345008550404>,$6=<2>,
> $7=<1>,$8=<NULL>,$9=<0.045037791453060014>,
> $10=<0.020180805785169714>,$11=<1.0>,$12=<NULL>,$13=<NULL>,
> $14=<387>,$15=<435>,$16=<3382993>)
>  FE=> Describe(portal=null)
>  FE=> Execute(portal=null,limit=0)
>  FE=> Parse(stmt=null,query=" SELECT currval('ip_audit_seq')",oids={})
>  FE=> Bind(stmt=null,portal=null)
>  FE=> Describe(portal=null)
>  FE=> Execute(portal=null,limit=0)
>  FE=> Sync
>  <=BE ParseComplete [null]
>  <=BE BindComplete [null]
>  <=BE NoData
>  <=BE CommandStatus(UPDATE 1)
>  <=BE ParseComplete [null]
>  <=BE BindComplete [null]
>  <=BE RowDescription(1)
>  <=BE DataRow
>  <=BE CommandStatus(SELECT)
>  <=BE ReadyForQuery(T)
> simple execute, handler=org.postgresql.jdbc2.AbstractJdbc2Connection
> $TransactionCommandHandler@1033450, maxRows=0, fetchSize=0, flags=22
>  FE=> Bind(stmt=S_2,portal=null)
>  FE=> Execute(portal=null,limit=1)
>  FE=> Sync
>  <=BE BindComplete [null]
>  <=BE CommandStatus(COMMIT)
>  <=BE ReadyForQuery(I)
>
> ---------------------------(end of
> broadcast)---------------------------
> TIP 6: explain analyze is your friend
>


Re: currval() race condition on server?

От
Tom Lane
Дата:
Adriaan Joubert <a.joubert@albourne.com> writes:
> It feels an awful lot like a timing issue where the sequence
> number is retrieved, but there is a delay until currval can use it. I'm
> not sure how currval works.

There is no "timing issue" in currval --- the server is single-threaded
and it's simply not possible that currval wouldn't be aware of a
previous nextval.

The theory that sounds best to me is the one someone already mentioned
about your trigger having a code path that doesn't execute nextval.
Another straw to grasp at is connection pooling: are you using it,
if so is it conceivable that the SELECT is being issued on a different
connection than the UPDATE?

            regards, tom lane

Re: currval() race condition on server?

От
Dave Cramer
Дата:
On 24-Oct-06, at 10:18 AM, Tom Lane wrote:

> Adriaan Joubert <a.joubert@albourne.com> writes:
>> It feels an awful lot like a timing issue where the sequence
>> number is retrieved, but there is a delay until currval can use
>> it. I'm
>> not sure how currval works.

Additionally since there is some confusion about how currval actually
works you need to be aware that currval is not necessarily the
current value of the sequence. It is the last value returned by
nextval for that connection. So what's the difference?

1) sequence = 1

2) con1 ->nextval .. sequence=2 currval = 2 for con1

3)con2 -> nextval .. sequence = 3 currval=3 for con2

4) now unless nextval is called again in connection 1 currval  will
still return  2

Dave
>
> There is no "timing issue" in currval --- the server is single-
> threaded
> and it's simply not possible that currval wouldn't be aware of a
> previous nextval.
>
> The theory that sounds best to me is the one someone already mentioned
> about your trigger having a code path that doesn't execute nextval.
> Another straw to grasp at is connection pooling: are you using it,
> if so is it conceivable that the SELECT is being issued on a different
> connection than the UPDATE?
>
>             regards, tom lane
>
> ---------------------------(end of
> broadcast)---------------------------
> TIP 9: In versions below 8.0, the planner will ignore your desire to
>        choose an index scan if your joining column's datatypes do not
>        match
>