Бывает ли состояние гонки (потеря обновления или перекос записи) в админке Django?

В Django views, мы можем использовать select_for_update() для предотвращения состояния гонки (потерянное обновление или перекос записи) так что race condition не происходит в Django views с select_for_update().

Но, даже если я погуглил, я не смог найти никакой информации о том, что админке Django состояние гонки не происходит или select_for_update() используется для предотвращения состояния гонки ".

".

Итак, в админке Django происходит ли состояние гонки?

  • Если да, то есть ли способы предотвратить состояние расы в администраторе Django?

  • Если нет, то используется ли select_for_update() или другой способ для предотвращения race condition в Django admin? и могу ли я увидеть код для меня?

По умолчанию в Django Admin, потерянное обновление или перекос записи, вызванный состоянием гонки может произойти, потому что select_for_update() не используется. * Мой ответ объясняет потерянное обновление и перекос записи.

Итак, я написал пример кода с select_for_update() для предотвращения потерянного обновления или перекоса записи в Django Admin, как показано ниже. * Я использовал Django 3.2.16 и PostgreSQL:

<Потерянное обновление>

Например, вы создаете store_product таблицу с id, name и stock с models.py, как показано ниже:

store_product таблица:

id name stock
1 Apple 10
2 Orange 20
# "store/models.py"

from django.db import models

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

Затем необходимо переопределить get_queryset() с select_for_update() в ProductAdmin():, как показано ниже:

# "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

Тогда, если вы измените (обновите) продукт, как показано ниже:

enter image description here

SELECT FOR UPDATE и UPDATE запросы выполняются в транзакции согласно журналам запросов PostgreSQL, как показано ниже. *Вы можете проверить как вести журнал запросов PostgreSQL:

enter image description here

И, если вы не переопределите get_queryset() в ProductAdmin():, как показано ниже:

# "store/admin.py"

from django.contrib import admin
from .models import Product

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

SELECT и UPDATE запросы выполняются, как показано ниже:

enter image description here

<Перекос записи>

Например, вы создаете store_doctor таблицу с id, name и on_call с models.py, как показано ниже:

store_doctor таблица:

id name on_call
1 John True
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()

Затем необходимо переопределить response_change() с select_for_update() и save_model() в DoctorAdmin(): как показано ниже. *Как минимум один врач должен быть на вызове:

# "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()

Тогда, если вы измените (обновите) doctor как показано ниже:

enter image description here

SELECT FOR UPDATE и UPDATE запросы выполняются в транзакции , но я не знаю, как удалить 1-й SELECT запрос светло-синим цветом, как показано ниже:

enter image description here

И, если вы не переопределите 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 и UPDATE запросы выполняются, как показано ниже:

enter image description here

Например, для write skew снова создается store_event таблица с id, name и user с models.py, как показано ниже:

store_event таблица:

id name user
1 Make Sushi John
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)

Затем, вам нужно переопределить response_add() с select_for_update() и save_model() в EventAdmin():, как показано ниже. *Только 3 пользователя могут присоединиться к событию "Приготовить суши" :

# "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()

Тогда, если вы добавите event, как показано ниже:

enter image description here

SELECT FOR UPDATE и INSERT запросы выполняются в транзакции , как показано ниже:

enter image description here

И, если вы не переопределите 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

Только INSERT запрос выполняется, как показано ниже:

enter image description here

Вы также можете посмотреть мои посты ниже о SELECT FOR UPDATE в Django:

Да, в админке Django может возникнуть состояние гонки, если несколько пользователей пытаются обновить один и тот же объект одновременно. Это может привести к потере обновлений или перекосу при записи, когда конечное значение объекта не соответствует задуманному. Чтобы избежать этого, Django admin использует оптимистическую блокировку, чтобы гарантировать, что обновления выполняются безопасно и последовательно. Это означает, что если два пользователя пытаются обновить один и тот же объект одновременно, одно из обновлений будет отклонено, и пользователю придется получить последнюю версию объекта и повторить попытку.

Вернуться на верх