Search code examples
pythondjangodjango-queryset

Atomic transaction on django queryset.update(**kwargs)


It seems Django's queryset.update method execute under transaction.atomic context manager. When would I need to do that explicitly in my code during update? Or what will be the benefits doing it, or problems not doing it?

Code

try:
    queryset = Model.objects.filter(a=1)
    if queryset.count():
        with transaction.atomic():
            queryset.update(a=2) # queryset will [] after this.
            for item in queryset: 
                item.emit_event('Updated')
except:
    logger.info('Exception')

My question is do I really need to have transaction.atomic(): here?

Secondly, after .update my queryset gets empty because its a filtered queryset. how to retain the values in my case as I want to emit an event on the individual objects.


Solution

  • Update 30.08.2023

    There is a new queryset method select_for_update in most cases you probably want to use it instead.

    First

    As docs state

    Atomicity is the defining property of database transactions. atomic allows us to create a block of code within which the atomicity on the database is guaranteed. If the block of code is successfully completed, the changes are committed to the database. If there is an exception, the changes are rolled back.

    In your example you would need atomic if emit_event is doing something and you want this update to be done only if all emit_event function calls and queryset.update are successfull. But if states of emit_event does not affect your business logic of update, atomic here would be redundant because as you said yourself update has internal atomic.

    Second

    Querysets are lazy. Which means evaluation of queryset will be done when you are iterating over it. So you need to do something like this. Answering latest comment

    try:
        queryset = Model.objects.filter(a=1)
        item_ids = list(queryset.values_list('id', flat=True)) # store ids for later
        if item_ids: # optimzing here instead of queryset.count() so there won't be hit to DB
            with transaction.atomic():
                queryset.update(a=2) # queryset will [] after this.
                for item in Model.objects.filter(id__in=item_ids):  # <- new queryset which gets only updated objects
                    item.emit_event('Updated')
    except:
        logger.info('Exception')
    

    See there we make new queryset when iterating over it to get updated items