简体   繁体   English

Django admin 是否发生竞争条件(丢失更新或写入偏斜)?

[英]Does race condition(lost update or write skew) happen in Django admin?

In Django views , we can use select_for_update() to prevent race condition(lost update or write skew) so race condition doesn't happen in Django views with select_for_update() .Django 视图中,我们可以使用select_for_update()来防止竞争条件(丢失更新或写入倾斜) ,因此在Django 视图中使用select_for_update()不会发生竞争条件 *I used Django 3.2.16 . *我用的是 Django 3.2.16

But, even though I googled, I couldn't find any information saying "in Django admin , race condition doesn't happen or select_for_update() is used to prevent race condition " .但是,即使我用谷歌搜索,我也找不到任何信息说“在Django admin中,竞争条件不会发生或select_for_update()用于防止竞争条件

So, in Django admin , does race condition happen?那么,在Django admin中,竞争条件会发生吗?

  • If yes, are there any ways to prevent race condition in Django admin ?如果是,是否有任何方法可以防止Django admin中的竞争条件

  • If no, is select_for_update() or other way used to prevent race condition in Django admin ?如果不是,是否使用select_for_update()或其他方式来防止Django admin中的竞争条件 and can I see the code for me?我可以看到我的代码吗?

By default in Django Admin , lost update or write skew caused by race condition happens because select_for_update() is not used.默认情况下,在Django Admin中,由于未使用select_for_update() ,会发生由于竞争条件导致的丢失更新写入倾斜 * My answer explains lost update and write skew . * 我的回答解释了 lost updatewrite skew

So, I wrote the example code with select_for_update() to prevent lost update or write skew in Django Admin as shown below.因此,我使用select_for_update()编写了示例代码,以防止丢失更新或在Django Admin写入倾斜,如下所示。 *I used Django 3.2.16 and PostgreSQL : *我使用了 Django 3.2.16PostgreSQL

< Lost update > <丢失更新>

For example, you create store_product table with id , name and stock with models.py as shown below:例如,您使用models.py创建带有idnamestockstore_product,如下所示:

store_product table: store_product表:

id ID name姓名 stock股票
1 1个 Apple苹果 10 10
2 2个 Orange橙子 20 20
# "store/models.py"

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=30)
    stock = models.IntegerField()

Then, you need to override get_queryset() with select_for_update() in ProductAdmin(): as shown below:然后,您需要在ProductAdmin():中用select_for_update()覆盖get_queryset() ,如下所示:

# "store/admin.py"

from django.contrib import admin
from .models import Product

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        
        last_part_of_referer = request.META.get('HTTP_REFERER').split('/')[-2]
        last_part_of_uri = request.build_absolute_uri().split('/')[-2]

        if (last_part_of_referer == "change" and last_part_of_uri == "change"):
            qs = qs.select_for_update()
        
        return qs

Then, if you change(update) product as shown below:然后,如果您更改(更新)产品,如下所示:

在此处输入图像描述

SELECT FOR UPDATE and UPDATE queries are run in transaction according to the PostgreSQL query logs as shown below. SELECT FOR UPDATEUPDATE查询根据PostgreSQL 查询日志事务中运行,如下所示。 *You can check how to log PostgreSQL queries : *您可以查看如何记录 PostgreSQL 查询

在此处输入图像描述

And, if you don't override get_queryset() in ProductAdmin(): as shown below:而且,如果您不覆盖 ProductAdmin( get_queryset() ProductAdmin():如下所示:

# "store/admin.py"

from django.contrib import admin
from .models import Product

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    pass

SELECT and UPDATE queries are run as shown below: SELECTUPDATE查询运行如下所示:

在此处输入图像描述

< Write skew > <写歪斜>

For example, you create store_doctor table with id , name and on_call with models.py as shown below:例如,您使用models.py创建带有idnameon_callstore_doctor,如下所示:

store_doctor table: store_doctor表:

id ID name姓名 on_call随传随到
1 1个 John约翰 True真的
2 2个 Lisa丽莎 True真的
# "store/models.py"

from django.db import models

class Doctor(models.Model):
    name = models.CharField(max_length=30)
    on_call = models.BooleanField()

Then, you need to override response_change() with select_for_update() and save_model() in DoctorAdmin(): as shown below:然后,您需要在DoctorAdmin():中用select_for_update()save_model()覆盖response_change() ,如下所示:

# "store/admin.py"

from django.contrib import admin
from .models import Doctor
from django.db import connection

@admin.register(Doctor)
class DoctorAdmin(admin.ModelAdmin):

    def response_change(self, request, obj):
        qs = super().get_queryset(request).select_for_update().filter(on_call=True)
        obj_length = len(qs)

        if obj_length == 0:
            obj.on_call = True
        obj.save()

        return super().response_change(request, obj)

    def save_model(self, request, obj, form, change):
        last_part_of_path = request.path.split('/')[-2]

        if last_part_of_path == "add":
            obj.save()

Then, if you change(update) doctor as shown below:然后,如果您更改(更新)医生,如下所示:

在此处输入图像描述

SELECT FOR UPDATE and UPDATE queries are run in transaction but I don't know how to remove the 1st SELECT query in light blue as shown below: SELECT FOR UPDATEUPDATE查询事务中运行,但我不知道如何删除浅蓝色的第一个SELECT查询,如下所示:

在此处输入图像描述

And, if you don't override response_change() and save_model() in DoctorAdmin(): as shown below:并且,如果您不覆盖 DoctorAdmin() 中的response_change()save_model() DoctorAdmin():如下所示:

# "store/admin.py"

from django.contrib import admin
from .models import Doctor

@admin.register(Doctor)
class DoctorAdmin(admin.ModelAdmin):
    pass

SELECT and UPDATE queries are run as shown below: SELECTUPDATE查询运行如下所示:

在此处输入图像描述

For example for write skew again, you create store_event table with id , name and user with models.py as shown below:例如,再次write skew ,您使用models.py创建带有idnameuserstore_event,如下所示:

store_event table: store_event表:

id ID name姓名 user用户
1 1个 Make Sushi做寿司 John约翰
2 2个 Make Sushi做寿司 Tom汤姆
# "store/models.py"

from django.db import models

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

Then, you need to override response_add() with select_for_update() and save_model() in EventAdmin(): as shown below:然后,您需要在EventAdmin():中用select_for_update()save_model()覆盖response_add() ,如下所示:

# "store/admin.py"

from django.contrib import admin
from .models import Event
from django.db import connection

@admin.register(Event)
class EventAdmin(admin.ModelAdmin):

    def response_add(self, request, obj, post_url_continue=None):
        qs = super().get_queryset(request).select_for_update().filter(name="Make Sushi")
        obj_length = len(qs)

        if obj_length < 3:
            obj.save()

        return super().response_add(request, obj, post_url_continue)

    def save_model(self, request, obj, form, change):
        last_part_of_path = request.path.split('/')[-2]

        if last_part_of_path == "change":
            obj.save()

Then, if you add event as shown below:然后,如果您添加事件,如下所示:

在此处输入图像描述

SELECT FOR UPDATE and INSERT queries are run in transaction as shown below: SELECT FOR UPDATEINSERT查询事务中运行,如下所示:

在此处输入图像描述

And, if you don't override response_add() and save_model() in EventAdmin(): as shown below:并且,如果您不重写 EventAdmin() 中的response_add()save_model() EventAdmin():如下所示:

# "store/admin.py"

from django.contrib import admin
from .models import Event

@admin.register(Event)
class EventAdmin(admin.ModelAdmin):
    pass

Only INSERT query is run as shown below:仅运行INSERT查询,如下所示:

在此处输入图像描述

Yes, a race condition can occur in Django admin if multiple users try to update the same object at the same time.是的,如果多个用户尝试同时更新同一个 object,Django admin 中可能会出现竞争条件。 This can result in lost updates or write skew, where the final value of the object is not what was intended.这可能会导致更新丢失或写入偏斜,其中 object 的最终值不是预期的值。 To avoid this, Django admin uses optimistic locking to ensure that updates are performed safely and consistently.为避免这种情况,Django 管理员使用乐观锁定来确保安全且一致地执行更新。 This means that if two users try to update the same object simultaneously, one of the updates will be rejected and the user will need to retrieve the latest version of the object and try again.这意味着如果两个用户尝试同时更新同一个 object,其中一个更新将被拒绝,用户将需要检索 object 的最新版本并重试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM