Are there any other cases which "select_for_update()" doesn't work but works with "print(qs)" in Django?

1k views Asked by At

I have Person model as shown below:

# "store/models.py"

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=30)

<CASE 1>

Then, when I use select_for_update() and update() of a queryset together as shown below (I use Django 3.2.16):

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):
                        # Here              # Here
    print(Person.objects.select_for_update().update(name="Tom"))
                        # Here                    # Here
    print(Person.objects.select_for_update().all().update(name="Tom"))
                        # Here                           # Here
    print(Person.objects.select_for_update().filter(id=1).update(name="Tom"))
                                                  
    return HttpResponse("Test")

Only UPDATE query is run without SELECT FOR UPDATE query as shown below. *I use PostgreSQL and these logs below are the queries of PostgreSQL and you can check on PostgreSQL, how to log queries with transaction queries such as "BEGIN" and "COMMIT":

enter image description here

But, when I use select_for_update() and update() of a queryset separately then put print(qs) between them as shown below:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):
                       
    qs = Person.objects.select_for_update()
    print(qs) # Here
    qs.update(name="Tom")

    qs = Person.objects.select_for_update().all()
    print(qs) # Here
    qs.update(name="Tom")

    qs = Person.objects.select_for_update().filter(id=1)
    print(qs) # Here
    qs.update(name="Tom")
                                                  
    return HttpResponse("Test")

SELECT FOR UPDATE and UPDATE queries are run as shown below:

enter image description here

In addition, when I use select_for_update() and save() of an object separately as shown below:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):
                            # Here
    person1 = Person.objects.select_for_update().first()
    person1.name = "Tom"
    person1.save() # Here
                            # Here
    person2 = Person.objects.select_for_update().all().first()
    person2.name = "Tom"
    person2.save() # Here
                            # Here
    person3 = Person.objects.select_for_update().filter(id=1).first()
    person3.name = "Tom"
    person3.save() # Here
                            # Here
    person4 = Person.objects.select_for_update().get(id=1)
    person4.name = "Tom"
    person4.save() # Here

    return HttpResponse("Test")

SELECT FOR UPDATE and UPDATE queries are run as shown below:

enter image description here

<CASE 2>

And, when I use select_for_update() and delete() of a queryset together as shown below:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):
                        # Here              # Here
    print(Person.objects.select_for_update().delete())
                        # Here                    # Here
    print(Person.objects.select_for_update().all().delete())
                        # Here                           # Here
    print(Person.objects.select_for_update().filter(id=1).delete())

    return HttpResponse("Test")

Only DELETE query is run without SELECT FOR UPDATE query as shown below.

enter image description here

But, when I use select_for_update() and delete() of a queryset separately then put print(qs) between them as shown below:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):

    qs = Person.objects.select_for_update()
    print(qs) # Here
    qs.delete()

    qs = Person.objects.select_for_update().all()
    print(qs) # Here
    qs.delete()

    qs = Person.objects.select_for_update().filter(id=1)
    print(qs) # Here
    qs.delete()
                                                  
    return HttpResponse("Test")

SELECT FOR UPDATE and DELETE queries are run as shown below:

enter image description here

In addition, when I use select_for_update() and delete() of an object together as shown below:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):
                        # Here              # Here
    print(Person.objects.select_for_update().first().delete())

    # Or
                        # Here                            # Here    
    print(Person.objects.select_for_update().all().first().delete())
    
    # Or
                        # Here                                   # Here
    print(Person.objects.select_for_update().filter(id=1).first().delete())

    # Or
                        # Here                        # Here
    print(Person.objects.select_for_update().get(id=1).delete())

    return HttpResponse("Test")

SELECT FOR UPDATE and DELETE queries are run as shown below:

enter image description here

enter image description here

enter image description here

enter image description here

I know QuerySets are lazy and When QuerySets are evaluated.

So, are there any other cases which select_for_update() doesn't work but works with print(qs) in Django in addition to what I've shown above?

1

There are 1 answers

0
Super Kai - Kazuya Ito On

Yes, there is other case which select_for_update() doesn't work but works with print(qs) in Django.

For example, when I use select_for_update() and count() of a queryset together as shown below:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):
                        # Here              # Here
    print(Person.objects.select_for_update().count())
                        # Here                    # Here
    print(Person.objects.select_for_update().all().count())
                        # Here                           # Here
    print(Person.objects.select_for_update().filter(id=1).count())

    return HttpResponse("Test")

SELECT query is run instead of SELECT FOR UPDATE query as shown below:

enter image description here

But, when I use select_for_update() and count() of a queryset separately then put print(qs) between them as shown below:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):

    qs = Person.objects.select_for_update()
    print(qs) # Here
    qs.count()

    qs = Person.objects.select_for_update().all()
    print(qs) # Here
    qs.count()

    qs = Person.objects.select_for_update().filter(id=1)
    print(qs) # Here
    qs.count()

    return HttpResponse("Test")

SELECT FOR UPDATE and SELECT queries are run as shown below but there are two queries SELECT FOR UPDATE and SELECT instead of one single query SELECT FOR UPDATE:

enter image description here

So to run one single query SELECT FOR UPDATE with the same result, I use select_for_update() and len() together which is a built-in function in Python as shown below:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):
        # Here              # Here
    print(len(Person.objects.select_for_update()))
        # Here              # Here
    print(len(Person.objects.select_for_update().all()))
        # Here              # Here
    print(len(Person.objects.select_for_update().filter(id=1)))

    return HttpResponse("Test")

Then, one single query SELECT FOR UPDATE is run as shown below:

enter image description here