我有一个非常大的数据库 (6 GB),我想使用 Django-REST-Framework。特别是,我有一个模型与 django.contrib.auth.models.User
表(不是很大)有外键关系,并且有一个 BIG 表的外键(我们称之为产品) .该模型如下所示:
class ShoppingBag(models.Model):
user = models.ForeignKey('auth.User', related_name='+')
product = models.ForeignKey('myapp.Product', related_name='+')
quantity = models.SmallIntegerField(default=1)
同样,有 6GB 的产品。
序列化器如下:
class ShoppingBagSerializer(serializers.ModelSerializer):
product = serializers.RelatedField(many=False)
user = serializers.RelatedField(many=False)
class Meta:
model = ShoppingBag
fields = ('product', 'user', 'quantity')
到目前为止这很好 - 我可以在列表和个人购物袋上执行 GET,一切都很好。作为引用,查询(使用查询记录器)看起来像这样:
SELECT * FROM myapp_product WHERE product_id=1254
SELECT * FROM auth_user WHERE user_id=12
SELECT * FROM myapp_product WHERE product_id=1404
SELECT * FROM auth_user WHERE user_id=12
...
因为有很多购物袋被退回。
但我希望能够POST
来创建新的购物袋,但是serializers.RelatedField
是只读的。让我们让它读写:
class ShoppingBagSerializer(serializers.ModelSerializer):
product = serializers.PrimaryKeyRelatedField(many=False)
user = serializers.PrimaryKeyRelatedField(many=False)
...
现在情况变得糟糕了... GET
请求 list
操作需要超过 5 分钟,我注意到我的服务器内存跳升至 ~6GB;为什么?!好吧,回到 SQL 查询,现在我看到了:
SELECT * FROM myapp_products;
SELECT * FROM auth_user;
好吧,那可不好。很明显,我们正在做“预取相关”或“选择相关”或类似的事情,以便访问所有产品;但是这张 table 很大。
进一步的检查揭示了在 Line 68 of relations.py in DRF 上发生这种情况的位置:
def initialize(self, parent, field_name):
super(RelatedField, self).initialize(parent, field_name)
if self.queryset is None and not self.read_only:
manager = getattr(self.parent.opts.model, self.source or field_name)
if hasattr(manager, 'related'): # Forward
self.queryset = manager.related.model._default_manager.all()
else: # Reverse
self.queryset = manager.field.rel.to._default_manager.all()
如果不是只读的,self.queryset = ALL!!
所以,我很确定这就是我的问题所在;我需要说,不要在这里选择相关,但如果这是问题或在哪里处理,我不是 100%。似乎所有的东西都应该是内存安全的分页,但事实并非如此。我将不胜感激任何建议。
最佳答案
最后,我们不得不简单地创建我们自己的 PrimaryKeyRelatedField
类来覆盖 Django-Rest-Framework 中的默认行为。基本上我们确保查询集是 None 直到我们想要查找对象,然后我们执行查找。这非常烦人,我希望 Django-Rest-Framework 的人注意这一点!
我们的最终解决方案:
class ProductField(serializers.PrimaryKeyRelatedField):
many = False
def __init__(self, *args, **kwargs):
kwarsgs['queryset'] = Product.objects.none() # Hack to ensure ALL products are not loaded
super(ProductField, self).__init__(*args, **kwargs)
def field_to_native(self, obj, field_name):
return unicode(obj)
def from_native(self, data):
"""
Perform query lookup here.
"""
try:
return Product.objects.get(pk=data)
except Product.ObjectDoesNotExist:
msg = self.error_messages['does_not_exist'] % smart_text(data)
raise ValidationError(msg)
except (TypeError, ValueError):
msg = self.error_messages['incorrect_type'] % type(data)
raise ValidationError(msg)
然后我们的序列化器如下:
class ShoppingBagSerializer(serializers.ModelSerializer):
product = ProductField()
...
此 hack 确保不会将整个数据库加载到内存中,而是根据数据执行一次性选择。它的计算效率不高,但它也不会用加载到内存中的 5 秒数据库查询来破坏我们的服务器!
关于django - DRF - 如何让 WritableField 不将整个数据库加载到内存中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22815534/