Thread: Locking issue

Locking issue

From
Andrew Jaimes
Date:
Hi everyone,

I am running 'PostgreSQL 9.1.4, compiled by Visual C++ build 1500, 64-bit' on a Windows Server and I am having some Locking issues. Maybe anyone can let me know what is wrong with my example:

Imagine that we have two tables (t_users and t_records)

 

t_users contains 1 row per each user

t_records is a regular transactional table which contains a field userid with the user that created/updated the row.

 

CREATE TABLE t_users(userid        VARCHAR(10),

                     loginattempts INTEGER,

                     CONSTRAINT pk_t_users PRIMARY KEY (userid));

 

CREATE TABLE t_records(recordid    INTEGER,

                       description VARCHAR(100),

                       userid      VARCHAR(10)

                       REFERENCES t_users(userid) MATCH SIMPLE

                       ON UPDATE NO ACTION ON DELETE NO ACTION);

                 

INSERT INTO t_users (userid, loginattempts) VALUES ('andrew',0);


 

 The user logs in and starts a background process that contains a long TRANSACTION which updates/inserts rows in t_records with the user's id.  This process keeps the transaction open for 1 hour while it works with code like:

                  

/* Session #1 */

BEGIN TRANSACTION

 

/* Big loop */

INSERT INTO t_records (recordid, description, userid) VALUES (1,'Record #1','andrew');

 

/*… SOME CODE HERE */

 

/*.... */

 

/* Once the loop ends, it will COMMIT/ROLLBACK  the transaction */

ROLLBACK / COMMIT


/* END of Session #1 */

 


The user logs out and then tries to log back in after 30 minutes.  The login hangs because we are not be able to update records on t_user  (for userids used on Session#1 ) until the transaction on Session#1 is done:

 

/* SESSION #2 */

 

UPDATE t_users  SET loginattempts = 1 WHERE userid = 'andrew'

 

/*END SESSION #2*/






 Any comments or feedback will be appreciated.


Regards,
Andrew Jaimes

Re: Locking issue

From
Josh Berkus
Date:
On 07/26/2016 01:16 AM, David Harrison wrote:
> Hi Josh,
>
> Attached is the function and below the query that calls it, below that the result of SELECT version();
>
> SELECT tl_guest_list('13313880', '174880', null, '151094636600', null, null);
>
>
>
>
>
> "PostgreSQL 8.4.9 on x86_64-unknown-linux-gnu, compiled by GCC gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-3), 64-bit”
>
> We have a job queue manager (beanstalkd) that we push jobs onto for it to process, previously there was only one
workerso tl_guest_list would only get called once at a time, after execution it would return the results and the next
jobwould run firing tl_guest_list again.  
>
> I’ve now upped the number of workers to 10 so it could be that 10 jobs pushed into the queue are all processed
concurrently.Firing tl_guest_list up to 10 times at the same time. I expect that the update on ste_seatspersessions is
lockingthe table and I expect the function to wait at the select on ste_seatspersessions. However the function is
processingthe select query and returning null. Seems like an issue with table locking or ignoring table locking. 


This is interesting:

  select seatid
  into seat
  from ste_seatspersessions sps join
      ste_seats s using (seatid) join
    ste_usergroupsaccessseatsets uss using (seat_setid)
  where sps.sessionid = ses and
      sps.rankid = rank and
  ...
    pg_try_advisory_lock(seatid)
  order by s.row_number, s.seat_number_in_row
  limit 1
  for update of sps;

You appear to be trying to implement your own "SKIP LOCKED" (which you
should maybe use instead).

I'm not sure this works as-is; SELECT FOR UPDATE with LIMIT/ORDER is
always fairly tricky, and tends to block the whole set, not just the
LIMITed row.

What I suggest is that you walk this through several concurrent sessions
yourself.  Use explicit transactions so that each concurrent session
will hold onto its locks.

--
--
Josh Berkus
Red Hat OSAS
(any opinions are my own)


Re: Locking issue

From
Thomas Munro
Date:
On Thu, Jul 28, 2016 at 10:18 AM, Josh Berkus <josh@agliodbs.com> wrote:
> On 07/26/2016 01:16 AM, David Harrison wrote:
>   where sps.sessionid = ses and
>         sps.rankid = rank and
>   ...
>     pg_try_advisory_lock(seatid)
>   order by s.row_number, s.seat_number_in_row
>   limit 1
>   for update of sps;

Don't you want pg_try_advisory_xact_lock (note "xact") here?
Otherwise you hold the advisory lock for the rest of the session,
unless you explicitly release it later.

> You appear to be trying to implement your own "SKIP LOCKED" (which you
> should maybe use instead).

+1

One difference between WHERE foo = 42 AND
pg_try_advisory_xact_lock(...) ... FOR UPDATE and WHERE foo = 42 ...
FOR UPDATE SKIP LOCKED is that the order of evaluation of the bits of
the WHERE clause linked by AND is probably undefined, so you could in
theory be acquiring advisory locks corresponding rows that don't have
foo = 42, depending on how the evaluator finished up processing that.
That could mess things up a bit for concurrent sessions.  SKIP LOCKED
on the other hand operates after any WHERE clause filtering.

> I'm not sure this works as-is; SELECT FOR UPDATE with LIMIT/ORDER is
> always fairly tricky, and tends to block the whole set, not just the
> LIMITed row.

There are complications with ORDER BY <something that might be
concurrently updated> FOR UPDATE, because row locking happens later
than ordering and causes you to see newer values that still match the
WHERE clause.  It gives you an isolation level more similar to READ
COMMITTED in non-snapshot based databases, except that the plan
implementing the ORDER BY didn't get the memo, and you'd have to fix
that with an outer query that sorts.  But I don't see why it would
block the whole set: LockRows sits on top and only gets its hands on
tuples emitted by nodes below it, so if there is a LIMIT then how
could it lock anything outside the limited set of rows that are
returned?

--
Thomas Munro
http://www.enterprisedb.com