Бывает ли состояние гонки (потеря обновления или перекос записи) в админке 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
Тогда, если вы измените (обновите) продукт, как показано ниже:
SELECT FOR UPDATE
и UPDATE
запросы выполняются в транзакции согласно журналам запросов PostgreSQL, как показано ниже. *Вы можете проверить как вести журнал запросов PostgreSQL:
И, если вы не переопределите 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
запросы выполняются, как показано ниже:
<Перекос записи>
Например, вы создаете 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 как показано ниже:
SELECT FOR UPDATE
и UPDATE
запросы выполняются в транзакции , но я не знаю, как удалить 1-й SELECT
запрос светло-синим цветом, как показано ниже:
И, если вы не переопределите 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
запросы выполняются, как показано ниже:
Например, для 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, как показано ниже:
SELECT FOR UPDATE
и INSERT
запросы выполняются в транзакции , как показано ниже:
И, если вы не переопределите 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
запрос выполняется, как показано ниже:
Вы также можете посмотреть мои посты ниже о SELECT FOR UPDATE в Django:
Как выполнить "SELECT FOR UPDATE" вместо "SELECT" при добавлении данных в Django Admin?
Как запустить "SELECT FOR UPDATE" вместо "SELECT" при изменении и удалении данных в Django Admin?
- Как выполнить "SELECT FOR UPDATE" для стандартного "Delete selected" в Django Admin Actions?
Да, в админке Django может возникнуть состояние гонки, если несколько пользователей пытаются обновить один и тот же объект одновременно. Это может привести к потере обновлений или перекосу при записи, когда конечное значение объекта не соответствует задуманному. Чтобы избежать этого, Django admin использует оптимистическую блокировку, чтобы гарантировать, что обновления выполняются безопасно и последовательно. Это означает, что если два пользователя пытаются обновить один и тот же объект одновременно, одно из обновлений будет отклонено, и пользователю придется получить последнюю версию объекта и повторить попытку.