我有一个非常大的数据库(6 GB),我想使用Django-REST-Framework。特别是,我有一个与django.contrib.auth.models.User
表(不是那么大)有一个ForeignKey关系的模型和一个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;
好的,所以这不好。很明显,我们正在做“预取相关”或“select_related”或类似的东西,以便访问所有产品;但这张桌子很大。
进一步检查会发现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()
如果不是readonly,self.queryset = ALL !!
所以,我很确定这就是我的问题所在;我需要说,不要在这里选择相关,但如果这是问题或者在哪里处理,我不是100%。似乎一切都应该是内存安全的分页,但事实并非如此。我很感激任何建议。
答案 0 :(得分:1)
最后,我们必须简单地创建自己的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秒数据库查询中爆炸我们的服务器!