Pessimitic locking and Optimistic locking in Django: Managing concurrent transactions
In previous article, i mentioned using select_for_update() to solve concurrent transactions. select_for_update() can be used to implement a form of pessimistic locking. So, in this article i will implement Optimistic locking in Django.
What is Optimistic locking?
Optimisitic locking is a strategy to handle concurrent transactions by using version number or timestamp. If two transactions attempt to update the same row has been modified since it was last read, ensuring no conflicting updates are commited.
Implementing Optimistic locking.
The steps to implement:
import time
from django.db import models
from django.db import transaction
from django.core.exceptions import ObjectDoesNotExist
class OptimisticLockError(Exception):
pass
class BankAccount(models.Model):
balance = models.DecimalField(max_digits=10, decimal_places=2)
version = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.pk is None:
super().save(*args, **kwargs)
else:
with transaction.atomic():
# Check if the version has changed in the database
current = BankAccount.objects.select_for_update().get(pk=self.pk)
time.sleep(3)
if current.version != self.version:
raise OptimisticLockError("The record has been modified by another transaction.")
# Increment the version and save
self.version += 1
super().save(*args, **kwargs
First, create a model name BankAccount with 2 fields: balance (current balance of the account), version (default = 0) in models.py
Override the save method to include logic for checking the version field and handling conflict.
Note: I add time.sleep to simulate 2 transactions occuring a few second apart.
In view.py, i add logic to handle OptimisticLockError to inform the user or retry the transaction.
领英推荐
from rest_framework import renderers, viewsets, status, pagination
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from rest_framework.response import Response
from .serializers import *
from .models import BankAccount, OptimisticLockError
from django.shortcuts import get_object_or_404, HttpResponse
class LockingViewSet(viewsets.ModelViewSet):
parser_classes = (FormParser, MultiPartParser)
serializer_class = BankAccountSerializer
def update(self, request, *args, **kwargs):
try:
amount = request.data.get('amount')
account = get_object_or_404(BankAccount, pk=int(kwargs.get('pk')))
account.balance += int(amount)
account.save()
return Response({"message":"Balance updated successfully."}, status=status.HTTP_200_OK)
except OptimisticLockError:
return Response({"message":"Conflict detected. Please try again."}, status=status.HTTP_409_CONFLICT)
If no conflict, return status code 200 else return conflict message.
To test it, i open 2 tab in postman, using the same url and update the same account. The second transaction will throw "Conflict detected. Please try again".
Choosing Pessimistic locking and Optimistic locking
The choice between pessimistic locking and optimistic locking depends on the characteristics of your application and the level of conflict expected.
Use pessimistic locking when:
Use pessimistic locking when:
What's next
In the next article, I will cover the topic of optimizing Django ORM.
? Backend Developer at CGT Media | Let's Connect! | #WeCommit100xHabit ?
10 个月Very informative
Astrophysicist | Python Developer
10 个月Useful tips!
? Chia s? v? nh?ng ?i?u mình bi?t, nh?ng sai l?m ?? g?p ph?i và gi?i pháp lu?n :D Coaching 1:1 thu?t toán và mindset t? duy Whatever you are not changing, you are choosing Let's connect!
10 个月I still see select_for_update. So it makes me confuse