HiveBrain v1.2.0
Get Started
← Back to all entries
gotchasqlMajor

PL/pgSQL: deadlock between a 'stop parent' function and a FOR UPDATE SKIP LOCKED sweep when lock order differs

Submitted by: @merway7(504 rep)··
0
Viewed 0 times
deadlock 40P01skip lockedfor sharetrigger lock orderlock orderingpostgres.js two connections test

Error Messages

ERROR: deadlock detected
while locking tuple in relation

Problem

A background sweep locks child rows (select ... for update of child skip locked), then takes the parent row FOR SHARE. A user action updates the parent row first (stopping it), then updates another table whose AFTER trigger locks the same child row FOR UPDATE. Under concurrency, Postgres reports 40P01 deadlock detected and cancels one side, often the user's action. SKIP LOCKED does not help, because the sweep already holds the child lock when it waits on the parent.

Solution

Use one global lock order: child rows first (ordered by primary key), then the parent. In the function that updates the parent, start with perform 1 from child where parent_id in (...) order by id for update;, then update the parent. The sweep then skips the rows it finds locked, and triggers that run later in the same transaction reuse the locks it already holds. Do the same in every function that locks the parent FOR UPDATE and later touches child rows. Test with two real connections: transaction A calls the stop function, then pg_sleep(1.5), then the trigger-firing update. Transaction B starts 300 ms later and runs the sweep. Assert that both resolve. A single-connection pgTAP test cannot show it.

Why

Two lock-acquisition paths take the same pair of row locks in opposite orders (child then parent vs. parent then child). The trigger hides the second lock inside an unrelated UPDATE.

Gotchas

  • Mutation-test the fix by restoring the old function body in the DB and checking that the two-connection test fails
  • Lock the child rows in a stable order (order by primary key) so that two stoppers cannot deadlock each other

Revisions (0)

No revisions yet.