Locking behavior

When you're using advisory locks in Postgres, lock calls stack when executed on the same connection (A.K.A. session):

  • Connection 1 calls pg_advisory_lock(42), acquires the lock and continues
  • Connection 1 calls pg_advisory_lock(42), this lock "stacks" with the previous call and continues
  • Connection 2 calls pg_advisory_lock(42), this blocks
  • Connection 1 calls pg_advisory_unlock(42), this pops one lock call off the stack and continues
  • Connection 1 calls pg_advisory_unlock(42), this pops the last lock call off the stack and continues
  • Connection 2 finally acquires the lock and continues

If you get connections from a pool (e.g. the standard sql library in Go maintains an internal pool of connections), you need to be aware of the locking behavior otherwise you might get unpredictable behavior or deadlock. You can get deterministic behavior by explicitly taking a connection from the pool (e.g. with db.Conn()).

Here's an example of bad code that can deadlock if the connection happens to be different across lock calls: ❌

// Grab a write lock
db.Exec("SELECT pg_advisory_lock(1)")
// Grab a read lock
db.Exec("SELECT pg_advisory_lock_shared(1)") // 💥 Can deadlock

Good code explicitly takes a connection out of the pool first ✅

conn := db.Conn()
// Grab a write lock
conn.Exec("SELECT pg_advisory_lock(1)")
// Grab a read lock
conn.Exec("SELECT pg_advisory_lock_shared(1)") // OK, will not block