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

Django仅用id实例化生产可用User模型避免额外SQL查询

问题:仅通过ID实例化Django空模型实例的生产可用方案

我想了解是否可以仅使用数据库记录的id实例化一个空的模型实例。我已经找到了创建测试用模拟模型的方法,但需要适用于生产环境的解决方案。

场景说明

我需要列出所有选择开启公开展示模式的用户的设置,初始写法如下:

user_displayed_list = UserPublicProfile.objects.filter(
    displayed = True,
    ).only(
        'user_id',
        'is_premium',
    )

user_settings_list = []
for user_displayed in user_displayed_list:
    # 需要将User实例传入后续方法
    user_settings = self.get_user_settings(user_displayed.user)
    user_settings_list.append(user_settings)
    # 但访问user_displayed.user会触发新的SQL查询

我知道可以用select_related优化查询集:

user_displayed_list = UserPublicProfile.objects.filter(
    displayed = True,
    ).select_related(
        'user'
    ).only(
        'user',
        'is_premium',
    )

但这种写法会产生不必要的表JOIN关联,因为我在get_user_settings()方法中仅需要用到user的id字段。

get_user_settings()方法示例(实际项目包含更多业务逻辑):

def get_user_settings(self, user)
    user_settings = UserSettings.objects.get(user = user)
    return user_settings

我的核心诉求:

  • 实例化一个仅填充id字段的真正的User模型对象,不接受自定义空类
  • 不需要额外数据库查询、不需要JOIN关联
  • 可直接传入依赖User实例的业务方法使用

期望的使用方式类似:

for user_displayed in user_displayed_list:
    FakeUser = User.objects.create_fake(id = user_displayed.user_id)
    # 将User实例传入后续方法
    user_settings = self.get_user_settings(FakeUser)

解决方案

Django原生支持这种用法,不需要额外依赖第三方库,直接实例化模型类传入主键即可,完全不会触发额外数据库查询,拿到的就是标准的User模型实例。

最简实现

直接手动创建User实例,只传id参数即可:

for user_displayed in user_displayed_list:
    # 仅传id实例化,不会访问数据库
    fake_user = User(id=user_displayed.user_id)
    user_settings = self.get_user_settings(fake_user)

注意:这个实例是内存中的临时对象,如果你后续调用save()方法且未补全其他必填字段,会触发数据库校验错误,仅用于外键传参查询的场景完全安全。
如果你需要避免误操作调用save,可以加上fake_user._state.adding = False,标记实例为已存在状态,就算误调用save也会走更新逻辑而非新建逻辑,但依然不建议用这个临时实例做写入操作。

实现原理

Django ORM处理外键关联查询时,只要传入的模型实例带有正确的主键id值,就会直接提取这个id做关联条件查询,不会访问实例上的其他字段,也不会触发实例的懒加载。你调用UserSettings.objects.get(user=user)时,ORM实际生成的SQL只会用到user实例的主键值,完全不会读取其他字段。

如果你后续需要访问user_displayed.user属性又不想触发额外查询,可以提前给外键缓存赋值:

for user_displayed in user_displayed_list:
    fake_user = User(id=user_displayed.user_id)
    # 提前填充外键缓存,后续访问user_displayed.user不会触发查询
    user_displayed._state.fields_cache["user"] = fake_user
    user_settings = self.get_user_settings(fake_user)

封装成你期望的调用方法

如果要在多处复用这个逻辑,可以给User模型加个类方法,完全实现你预期的create_fake效果:

from django.db import models

class User(models.Model):
    # 原有User模型字段
    username = models.CharField(max_length=150)
    # ... 其他业务字段
    
    @classmethod
    def create_fake(cls, id):
        instance = cls(id=id)
        instance._state.adding = False
        return instance

封装后就可以直接按你写的方式调用:

for user_displayed in user_displayed_list:
    fake_user = User.create_fake(id=user_displayed.user_id)
    user_settings = self.get_user_settings(fake_user)

性能对比

  • 初始写法:循环N次触发N次User表查询,存在典型的N+1问题
  • select_related写法:1次SQL带JOIN联查User表字段
  • 本方案:仅1次查询UserPublicProfile表的SQL,无JOIN、无额外查询,性能最优

内容的提问来源于stack exchange,提问作者Samuel Dauzon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 09:33:39