Re: Unexpected deadlock across two separate rows, using Postgres 17 and Django's select_for_update() - Mailing list pgsql-general

From Shaheed Haque
Subject Re: Unexpected deadlock across two separate rows, using Postgres 17 and Django's select_for_update()
Date
Msg-id CAHAc2jeME7KPPP3TgP9qWC5LMTOs4gYTEJ1MBDwnj7J2EvJTdA@mail.gmail.com
Whole thread Raw
In response to Re: Unexpected deadlock across two separate rows, using Postgres 17 and Django's select_for_update()  (felix.quintgz@yahoo.com)
Responses Re: Unexpected deadlock across two separate rows, using Postgres 17 and Django's select_for_update()
List pgsql-general
On Sun, 8 Mar 2026 at 15:15, <felix.quintgz@yahoo.com> wrote:
This is pure speculation.
It's possible that using SELECT FOR UPDATE also locks the rows in the parent tables referenced in the field list.
I believe this happened in older versions of PostgreSQL.

Interesting. In the query, paiyroll_endpoint.op_id and paiyroll_endpoint.client_id ARE foreign keys to other tables.

But I don't see any reference to locking rows in parent tables in the docs around https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-ROWS. A quick poke around did not reveal any documentation that confirms this one way or another. And to my admittedly in-expert thinking, it seems surprising that the parent might need to be locked?

 

 On Saturday, March 7, 2026 at 04:25:01 AM GMT-5, Shaheed Haque <shaheedhaque@gmail.com> wrote:

 [I originally posted this over at https://forum.djangoproject.com/t/unexpected-deadlock-across-two-separate-rows-using-postgres-17-and-select-for-update/44294/1, but that thread ran into a dead end. Apologies for the cross-post]

Hi,
I'm trying to understand/fix a rare deadlock in my application. Given my limited knowledge, what seems odd to me is that the deadlock involves two processes running exactly the same code/query, each of which (tries to) avoid issues by locking exactly one row for update. In Django-speak, the code does this:

#
# Select-for-update exactly one row by id.
#
qs = Endpoint.objects.select_for_update().filter(id=instance.id)
#
# The above returns a queryset of one row which we loop over:
#
for item in qs:

 ...do stuff with item...

 item.save() The deadlock is reported in the Postgres server log like this:
ERROR: deadlock detected

DETAIL: Process 15576 waits for ShareLock on transaction 31053599; blocked by process 16953.

Process 16953 waits for ShareLock on transaction 31053597; blocked by process 15576.

Process 15576: SELECT “paiyroll_endpoint”.“id”,
“paiyroll_endpoint”.“op_id”, “paiyroll_endpoint”.“client_id”,
“paiyroll_endpoint”.“client_private”, “paiyroll_endpoint”.“netloc”,
“paiyroll_endpoint”.“calls”, “paiyroll_endpoint”.“ms”,
“paiyroll_endpoint”.“history”, “paiyroll_endpoint”.“current_history”
FROM “paiyroll_endpoint” WHERE “paiyroll_endpoint”.“id” = 1 FOR UPDATE

Process 16953: SELECT “paiyroll_endpoint”.“id”,
“paiyroll_endpoint”.“op_id”, “paiyroll_endpoint”.“client_id”,
“paiyroll_endpoint”.“client_private”, “paiyroll_endpoint”.“netloc”,
“paiyroll_endpoint”.“calls”, “paiyroll_endpoint”.“ms”,
“paiyroll_endpoint”.“history”, “paiyroll_endpoint”.“current_history”
FROM “paiyroll_endpoint” WHERE “paiyroll_endpoint”.“id” = 2 FOR UPDATE

HINT: See server log for query details.

CONTEXT: while locking tuple (7,15) in relation “paiyroll_endpoint”

STATEMENT: SELECT “paiyroll_endpoint”.“id”,
“paiyroll_endpoint”.“op_id”, “paiyroll_endpoint”.“client_id”,
“paiyroll_endpoint”.“client_private”, “paiyroll_endpoint”.“netloc”,
“paiyroll_endpoint”.“calls”, “paiyroll_endpoint”.“ms”,
“paiyroll_endpoint”.“history”, “paiyroll_endpoint”.“current_history”
FROM “paiyroll_endpoint” WHERE “paiyroll_endpoint”.“id” = 1 FOR UPDATE
How can there be a deadlock between updates to different rows (as per the bolded WHERE clauses)? Have I somehow turned off row-level locks? Is there some additional logging I could enable to try to catch the data needed to root-cause this?

Any help appreciated.
Thanks, Shaheed


pgsql-general by date:

Previous
From: felix.quintgz@yahoo.com
Date:
Subject: Re: Unexpected deadlock across two separate rows, using Postgres 17 and Django's select_for_update()
Next
From: Laurenz Albe
Date:
Subject: Re: Unexpected deadlock across two separate rows, using Postgres 17 and Django's select_for_update()