Controlling duration of PostgreSQL lock waits

I assume FOR UPDATE is locking the table so that we can manipulate it without another thread stomping on the data.

Nope. FOR UPDATE locks only those rows, so that another transaction that attempts to lock them (with FOR SHARE, FOR UPDATE, UPDATE or DELETE) blocks until your transaction commits or rolls back.

If you want a whole table lock that blocks inserts/updates/deletes you probably want LOCK TABLE ... IN EXCLUSIVE MODE.

  1. Subsequent queries trying to get the lock should fail, I have tried achieving this with NOWAIT but would prefer a timeout method (because it may be ok to wait, just not wait for a ‘stupid amount of time’)

    See the lock_timeout setting. This was added in 9.3 and is not available in older versions.

    Crude approximations for older versions can be achieved with statement_timeout, but that can lead to statements being cancelled unnecessarily. If statement_timeout is 1s and a statement waits 950ms on a lock, it might then get the lock and proceed, only to be immediately cancelled by a timeout. Not what you want.

    There’s no query-level way to set lock_timeout, but you can and should just:

    SET LOCAL lock_timeout="1s";

    after you BEGIN a transaction.

  2. Ideally I would head this off at the pass, and have my initial query only hold the lock for a certain amount of time, is this possible with postgresql?

    There is a statement timeout, but locks are held at transaction level. There’s no transaction timeout feature.

    If you’re running single-statement transactions you can just set a statement_timeout before running the statement to limit how long it can run for. This isn’t quite the same thing as limiting how long it can hold a lock, though, because it might wait 900ms of an allowed 1s for the lock, only actually hold the lock for 100ms, then get cancelled by the timeout.

  3. Is there some other magic function I can tack onto the query (similar to NOWAIT) which will only wait for the lock for 4 seconds before failing?

    No. You must:

    SET LOCAL lock_timeout="4s";
    SELECT ....;
  4. Due to the painfully monolithic spaghetti code nature of the code base, its not simply a matter of changing global configs, it kinda needs to be a per-query based solution

    SET LOCAL is suitable, and preferred, for this.

    There’s no way to do it in the text of the query, it must be a separate statement.

    The mailing list post you linked to is a proposal for an imaginary syntax that was never implemented (at least in a public PostgreSQL release) and does not exist.

In a situation like this you may want to consider “optimistic concurrency control”, often called “optimistic locking”. It gives you greater control over locking behaviour at the cost of increased rates of query repetition and the need for more application logic.

Leave a Comment