Django loaddata - 内存不足
6 个回答
我在把数据从微软的SQL Server迁移到PostgreSQL时遇到了问题,所以我不能使用sqldump
和pg_dump
这两个工具。我把我的json数据分成了几块,每块的大小适合我的内存(对于一个宽表来说,大约有100万行数据,而我的电脑有64GB的内存)。
def dump_json(model, batch_len=1000000):
"Dump database records to a json file in Django fixture format, one file for each batch of 1M records"
JSONSerializer = serializers.get_serializer("json")
jser = JSONSerializer()
for i, partial_qs in enumerate(util.generate_slices(model.objects.all(), batch_len=batch_len)):
with open(model._meta.app_label + '--' + model._meta.object_name + '--%04d.json' % i, 'w') as fpout:
jser.serialize(partial_qs, indent=1, stream=fpout)
然后你可以用manage.py loaddata <app_name>--<model_name>*.json
来加载这些数据。不过在我的情况下,我需要先用sed
命令修改文件里的模型和应用名称,以确保它们能加载到正确的数据库里。我还把主键(pk)设为null,因为我把主键改成了AutoField
(这是django的最佳实践)。
sed -e 's/^\ \"pk\"\:\ \".*\"\,/"pk": null,/g' -i *.json
sed -e 's/^\ \"model\"\:\ \"old_app_name\.old_model_name\"\,/\ \"model\"\:\ "new_app_name\.new_model_name\"\,/g' -i *.json
你可能会觉得pug这个工具很有用。它是一个开源的Python包,里面有一些处理大规模迁移和数据挖掘任务的工具,适合在django中使用。
你可以使用XML格式来进行数据的保存和读取。它在内部是通过文件流来实现的,相比于JSON格式,它占用的内存要少一些。不过,Django在处理JSON数据时并没有使用文件流。
所以你可以试试:
./manage.py dumpdata file.xml
然后再试试
./manage.py loaddata file.xml
我想补充一下,我在一个类似的场景中用ijson取得了不错的效果:https://github.com/isagalaev/ijson
为了从Django的dumpdata生成的json文件中获取对象的迭代器,我对json反序列化器进行了如下修改(省略了一些导入部分):
Serializer = django.core.serializers.json.Serializer
def Deserializer(stream_or_string, **options):
if isinstance(stream_or_string, six.string_types):
stream_or_string = six.BytesIO(stream_or_string.encode('utf-8'))
try:
objects = ijson.items(stream_or_string, 'item')
for obj in PythonDeserializer(objects, **options):
yield obj
except GeneratorExit:
raise
except Exception as e:
# Map to deserializer error
six.reraise(DeserializationError, DeserializationError(e), sys.exc_info()[2])
直接使用py-yajl的问题在于,它会把所有对象放在一个大数组里,这样会占用很多内存。而我这个循环只会使用一个序列化的Django对象所需的内存。此外,ijson仍然可以使用yajl作为后端。
正如乔所提到的,PostgreSQL的pg_dump或者MySQL的mysqldump更适合你的情况。
如果你丢失了原来的数据库,有两种方法可以尝试找回你的数据:
第一种:找一台内存更大、能访问你数据库的机器。在那台机器上搭建你的项目,然后运行loaddata命令。
我知道这听起来有点傻。但如果你能在笔记本上运行django,并且可以远程连接到数据库,这其实是最快的方法。
第二种:修改Django的源代码。
查看django.core.serializers.json.py中的代码:
def Deserializer(stream_or_string, **options):
"""
Deserialize a stream or string of JSON data.
"""
if not isinstance(stream_or_string, (bytes, six.string_types)):
stream_or_string = stream_or_string.read()
if isinstance(stream_or_string, bytes):
stream_or_string = stream_or_string.decode('utf-8')
try:
objects = json.loads(stream_or_string)
for obj in PythonDeserializer(objects, **options):
yield obj
except GeneratorExit:
raise
except Exception as e:
# Map to deserializer error
six.reraise(DeserializationError, DeserializationError(e), sys.exc_info()[2])
下面的代码就是问题所在。标准库中的json
模块只接受字符串,不能懒加载处理流数据。所以Django会把整个json文件的内容都加载到内存中。
stream_or_string = stream_or_string.read()
objects = json.loads(stream_or_string)
你可以用py-yajl来优化这些代码。py-yajl提供了一种替代内置的json.loads和json.dumps的方法,使用的是yajl。
loaddata
通常是用来加载一些小的数据库数据,比如说初始化系统或进行测试,而不是用来处理大量的数据。如果你在使用的时候遇到内存限制,那可能是你没有正确使用这个功能。
如果你还有原来的数据库,建议你使用更合适的工具,比如 PostgreSQL 的 pg_dump
或 MySQL 的 mysqldump
。