# Unexpected deadlock across two separate rows, using Postgres 17 and select\_for\_update().

**URL:** <https://forum.djangoproject.com/t/unexpected-deadlock-across-two-separate-rows-using-postgres-17-and-select-for-update/44294>\
**Category:** Using the ORM\
**Created:** [February 25, 2026, 5:05pm UTC](https://forum.djangoproject.com/t/unexpected-deadlock-across-two-separate-rows-using-postgres-17-and-select-for-update/44294 "2026-02-25T17:05:08Z")\
**Posts on this page:** 1\
**Showing post:** 1

<div class="post-metadata">

**Author:** ![ShaheedHaque](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/shaheedhaque/32/17659_2.png) [@ShaheedHaque](https://forum.djangoproject.com/u/ShaheedHaque)\
**Post date:** [February 25, 2026, 5:05pm UTC](https://forum.djangoproject.com/t/unexpected-deadlock-across-two-separate-rows-using-postgres-17-and-select-for-update/44294/1 "2026-02-25T17:05:08Z")

</div>

Hi,

I have a table of Endpoints which looks like this:

```auto
class Endpoint(db_models.Model):
    ...
    op = db_models.ForeignKey(ReportOp, on_delete=db_models.CASCADE)
    client = db_models.ForeignKey(Client, on_delete=db_models.CASCADE, null=True, blank=True)
    client_private = db_models.IntegerField(_('Client id or zero'), blank=True)
    netloc = db_models.URLField(_('URL'), help_text=_('...'))
    ...

    ... various data fields...

    ...

    class Meta:
        unique_together = ('op', 'netloc', 'client_private')

```

I modify one row at a time in this table from separate Linux gunicorn/django processes, using a single point in the code, which looks like this:

> ```auto
> def locked_instance(self, ...):
> 
> // Ensure the row exists.
> 
> with transaction.atomic():
> instance, created = Endpoint.objects.get_or_create(kwargs | {'client': client}, **kwargs)
> ...initialise the various data fields...
> 
> // Return locked reference to the row
> 
> qs = Endpoint.objects.select_for_update().filter(id=instance.id)
> return qs
> 
> ```

To my surprise, I got a deadlock where the Postgres server log shows two DIFFERENT rows (id = 1 and id = 2 below) being involved. Here is the server log:

> 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 two separate rows? I guess this means there is a problem with my understanding and/or the code, but I’m not sure what that could be. I have looked into the “select\_for\_update(no\_key=True)” rowlock docs for Postgres, but I confess to being somewhat out of my depth. Any pointers would be most welcome…

Thanks, Shaheed

---

_[View the full topic](https://forum.djangoproject.com/t/unexpected-deadlock-across-two-separate-rows-using-postgres-17-and-select-for-update/44294)._
