Why does .exists() respect .select_for_update() while .count() doesn't?

Hello, I’ve just noticed that when I do

MyModel.objects.select_for_update().count() != 0

it runs successfully. However, when I do


I get an error select_for_update cannot be used outside of a transaction.

I checked the queries that django made and they are as follows:

SELECT COUNT(*) AS "__count" FROM "example_mymodel"          # .count()
SELECT (1) AS "a" FROM "example_mymodel" LIMIT 1 FOR UPDATE  # .exists()

I tested it by creating a completely new app in my existing django project (django==3.2.10 + PostgreSQL 12.0.1) and this is the model I used:

class MyModel(models.Model):
    my_field = models.CharField(max_length=256)

However, when I created a completely new django project (django==3.2.10 + SQLite 3.31.1) with the same app and model, the .exists() query passed without wrapping it with transaction.atomic(). This is the query in the second test

SELECT (1) AS "a" FROM "example_mymodel" LIMIT 1

Note that it doesn’t contain FOR UPDATE.

All tests were done via ./manage.py shell and looked up with django.connection.queries.

Is this expected? Why is there such discrepancy between .count() and .exists() or different databases?

.exists() and SQLite work as documented here QuerySet API reference | Django documentation | Django

Evaluating a queryset with select_for_update() in autocommit mode on backends which support SELECT ... FOR UPDATE is a TransactionManagementError error because the rows are not locked in that case.


Using select_for_update() on backends which do not support SELECT ... FOR UPDATE (such as SQLite) will have no effect.

As for the different behavior of .count() I’m not really sure.