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

请求解决Django_mailbox访问message.html时重复数据库查询问题

解决Django Mailbox访问Message.html时的重复数据库查询问题

问题背景

访问Message对象的.html属性时,会触发大量重复的数据库查询,即便已经通过select_related和prefetch_related预加载了关联数据。

原代码

messagesDB = Message.objects.filter(mailbox=mailbox).select_related('mailbox', 'message_used').prefetch_related(Prefetch('attachments', queryset=MessageAttachment.objects.all(), to_attr='all_attachments'))
for message in messagesDB:
    print(message.html)

重复查询日志

SELECT ••• FROM "django_mailbox_messageattachment" WHERE "django_mailbox_messageattachment"."id" = 44 LIMIT 21
 13 similar queries.


SELECT "django_mailbox_messageattachment"."id",
       "django_mailbox_messageattachment"."message_id",
       "django_mailbox_messageattachment"."headers",
       "django_mailbox_messageattachment"."document"
  FROM "django_mailbox_messageattachment"
 WHERE "django_mailbox_messageattachment"."id" = 44
 LIMIT 21 13 similar queries.

调用栈

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\django\contrib\staticfiles\handlers.py in __call__(80)
  return self.application(environ, start_response)

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\whitenoise\middleware.py in __call__(124)
  return self.get_response(request)

C:\Users\user\AppData\Local\Programs\Python\Python310\lib\contextlib.py in inner(79)
  return func(*args, **kwds)

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\django\views\generic\base.py in view(104)
  return self.dispatch(request, *args, **kwargs)

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\django\contrib\auth\mixins.py in dispatch(73)
  return super().dispatch(request, *args, **kwargs)

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\django\contrib\auth\mixins.py in dispatch(109)
  return super().dispatch(request, *args, **kwargs)

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\django\views\generic\base.py in dispatch(143)
  return handler(request, *args, **kwargs)

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\django\views\generic\base.py in get(226)
  context = self.get_context_data(**kwargs)

C:\Users\user\Desktop\Задачник\ProjectsDashboard\dashboard\timetable\views.py in get_context_data(2109)
  messagesDB = self.get_message_telegram(self.mailbox)

C:\Users\user\Desktop\Задачник\ProjectsDashboard\dashboard\timetable\views.py in get_message_telegram(2058)
  print(message.html)

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\django_mailbox\models.py in html(671)
  self.get_email_object(), 'text', 'html'

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\django_mailbox\models.py in get_email_object(783)
  self._email_object = self._rehydrate(flat)

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\django_mailbox\models.py in _rehydrate(683)
  self._rehydrate(part)

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\django_mailbox\models.py in _rehydrate(683)
  self._rehydrate(part)

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\django_mailbox\models.py in _rehydrate(687)
  attachment = MessageAttachment.objects.get(

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\cacheops\query.py in get(327)
  return qs._no_monkey.get(qs, *args, **kwargs)

C:\Users\user\.virtualenvs\dashboard-APxxHYiU\lib\site-packages\cacheops\query.py in _fetch_all(250)
  return self._no_monkey._fetch_all(self)

解决方案

从调用栈可知,问题根源在于django_mailbox的_rehydrate方法重建邮件对象时,直接调用MessageAttachment.objects.get()查询附件,完全未使用预加载的all_attachments属性,导致重复查询。以下是可行的解决方法:

方法1:猴子补丁修改_rehydrate逻辑

通过补丁让_rehydrate优先使用预加载的附件,避免重复查询:

from django_mailbox.models import Message

original_rehydrate = Message._rehydrate

def patched_rehydrate(self, part):
    if hasattr(self, 'all_attachments') and part.get('attachment_id'):
        # 从预加载的all_attachments中匹配附件
        for attachment in self.all_attachments:
            if attachment.id == part['attachment_id']:
                part['payload'] = attachment.document.read()
                return part
    # 原逻辑兜底
    return original_rehydrate(self, part)

Message._rehydrate = patched_rehydrate

将这段代码放在项目初始化位置(如settings.py末尾、apps.py的ready方法),确保启动时生效。

方法2:预加载后缓存附件内容

提前读取附件内容并缓存到Message对象,再通过补丁读取缓存:

# 查询时预加载附件
messagesDB = Message.objects.filter(mailbox=mailbox).select_related('mailbox', 'message_used').prefetch_related(Prefetch('attachments', queryset=MessageAttachment.objects.all(), to_attr='all_attachments'))

# 提前缓存附件内容
for message in messagesDB:
    message._cached_attachments = {
        att.id: att.document.read() for att in message.all_attachments
    }
    print(message.html)

搭配对应的猴子补丁:

from django_mailbox.models import Message

original_rehydrate = Message._rehydrate

def patched_rehydrate(self, part):
    if hasattr(self, '_cached_attachments') and part.get('attachment_id'):
        part['payload'] = self._cached_attachments.get(part['attachment_id'], b'')
        return part
    return original_rehydrate(self, part)

Message._rehydrate = patched_rehydrate

方法3:修改django_mailbox源码(不推荐)

直接修改django_mailbox/models.py中的_rehydrate方法,将attachment = MessageAttachment.objects.get(id=part['attachment_id'])替换为从预加载关联属性中查找。但该方法会在包更新后失效,仅适合临时应急或fork维护场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 06:55:02