[Yugong series] February 2022 Python teaching course: pessimistic lock and optimistic lock of 58 Django framework

Posted by aleph_x on Mon, 07 Feb 2022 01:43:31 +0100

Article catalogue

preface

In high concurrency scenarios such as e-commerce spike, data conflicts cannot be avoided by just starting transactions. For example, user A and user B obtain the inventory of A commodity and try to modify it. The inventory of commodities queried by A and B is 5. As A result, A orders 5 and B also orders 5. This is A problem. The solution is to lock the inventory information of A commodity when operating (querying or modifying) it. There are pessimistic locks and optimistic locks.

1. Pessimistic lock

It is always assumed that in the worst case, every time you go to get the data, you think others will modify it, so every time you get the data, you will lock it, so that others will block the data until they get the lock (shared resources are only used by one thread at a time, blocked by other threads, and then transfer the resources to other threads after they are used up). Many of these locking mechanisms are used in traditional relational databases, such as row lock, table lock, read lock and write lock, which are locked before operation.

2. Optimistic lock

Always assume the best situation. Every time you go to get the data, you think others will not modify it, so it will not be locked. However, when updating, you will judge whether others have updated the data during this period. You can use the version number mechanism and CAS algorithm. Optimistic locking is applicable to multi read applications, which can improve throughput. It is similar to write provided by the database_ The condition mechanism is actually an optimistic lock provided.

1, Pessimistic lock in Django

To lock an object with pessimistic lock in Django, you need to use select_for_update() method. It is essentially a row level lock, which can lock all matching rows until the end of the transaction.

1. Pessimistic lock case

# Locked case: SKU class, view id = 1
class OrderView(APIView):
    @transaction.atomic
    def post(self, request):
        # select_for_update indicates the lock. The query will be executed only when the lock is obtained. Otherwise, the wait will be blocked.
        sku = GoodsSKU.objects.select_for_update().get(id=10)
        # After the transaction is committed, the lock will be released automatically.
        ......
        return Response("xxx")
# Case 2: function view, lock the list of all qualified article objects.
from django.db import transaction

with transaction.atomic():
    entries = Entry.objects.select_for_update().filter(author=request.user)
    for entry in entries:
        ...

Generally, if other transactions lock related rows, this query will be blocked until the lock is released. If you don't want to block the query, use select_for_update(nowait=True). Note:

  • select_ for_ The update method must be used in conjunction with a transaction.
  • MySQL version must be above 8.0.1 + to support nowait and of options.

2. Associated object locking

# Only entry(self) and category will be locked, and author will not be locked
entries = Entry.objects.select_related('author', 'category'). select_for_update(of=('self', 'category'))

When you use select at the same time_ for_ Update and select_ When the related method, select_ The related object specified by related will also be locked. You can select_ for_ The update (of = (...)) method specifies the associated object to be locked.

2, Optimistic lock in Django

The implementation of optimistic lock in Django project can rely on the third-party library Django concurrency, which can add a version field to the model and automatically give the version number + 1 each time the save operation is performed.

 from django.db import models
 from concurrency.fields import IntegerVersionField
 
 
 class ConcurrentModel( models.Model ):
     version = IntegerVersionField( )
     name = models.CharField(max_length=100)

In the following example, a and b obtain the model object information with pk=1 at the same time and try to modify its name field. Since the version number of the object has been increased by 1 after the successful call of a.save() method, b will report the error of RecordModifiedError when calling b.save() method, so as to avoid data conflict caused by a and b modifying the information of the same object at the same time.

a = ConcurrentModel.objects.get(pk=1)
a.name = '1'

b = ConcurrentModel.objects.get(pk=1)
b.name = '2'

a.save()
b.save()

summary

  • Pessimistic lock: applicable to scenarios with multiple writes
  • Optimistic lock: applicable to multi read scenarios