You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Django Admin添加数据时执行SELECT FOR UPDATE而非SELECT

问题原因

你遇到的情况是因为select_for_update()的锁需要在事务上下文中才能生效,且直接调用count()时,若不在事务内,PostgreSQL会自动开启并立即提交临时事务,导致锁无法持续到后续的保存操作,同时Django ORM在非事务环境下对count()的select_for_update()处理可能未正确生成带锁的查询。

另外,你通过解析URL判断修改页面的方式不够健壮,建议利用save_model自带的change参数来区分添加/修改操作。

解决方案
  1. 导入Django的事务模块,在response_add中用原子事务包裹查询与保存逻辑,确保锁的有效性和操作原子性;
  2. 优化save_model的判断逻辑,使用change参数替代URL解析;
  3. 移除冗余的all()调用(get_queryset()返回的已是QuerySet对象,无需额外调用all())。

修改后的代码如下:

# "store/admin.py"

from django.contrib import admin
from django.db import transaction  # 导入事务模块
from .models import Person

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):

    def response_add(self, request, obj, post_url_continue=None):
        # 用原子事务包裹,确保查询锁和保存操作在同一事务中
        with transaction.atomic():
            # 获取带行锁的查询集(包含admin权限过滤)
            queryset = super().get_queryset(request).select_for_update()
            obj_count = queryset.count()
            
            if obj_count < 2:
                obj.save()

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

    def save_model(self, request, obj, form, change):
        # change为True表示是修改页面,False为添加页面
        if change:
            obj.save()
原理说明
  • transaction.atomic()会创建一个数据库事务,在事务内执行select_for_update()的查询时,会对匹配的行加排他锁,直到事务提交/回滚,这样就能阻止其他事务同时修改或查询这些行,避免写入偏斜;
  • 优化后的save_model通过change参数判断操作类型,比URL解析更可靠且符合Django Admin的设计规范;
  • 移除all()后,QuerySet的调用更简洁,不影响功能。

内容的提问来源于stack exchange,提问作者Super Kai - Kazuya Ito

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 10:45:42