如何在Django Admin添加数据时执行SELECT FOR UPDATE而非SELECT
问题原因
你遇到的情况是因为select_for_update()的锁需要在事务上下文中才能生效,且直接调用count()时,若不在事务内,PostgreSQL会自动开启并立即提交临时事务,导致锁无法持续到后续的保存操作,同时Django ORM在非事务环境下对count()的select_for_update()处理可能未正确生成带锁的查询。
另外,你通过解析URL判断修改页面的方式不够健壮,建议利用save_model自带的change参数来区分添加/修改操作。
解决方案
- 导入Django的事务模块,在
response_add中用原子事务包裹查询与保存逻辑,确保锁的有效性和操作原子性; - 优化
save_model的判断逻辑,使用change参数替代URL解析; - 移除冗余的
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
相关产品推荐
相关产品推荐

