Django loaddata - 内存不足

9 投票
6 回答
7247 浏览
提问于 2025-04-18 02:36

我用 dumpdata 命令把我的数据库备份了一下,结果生成了一个500MB的json文件。

现在我想用 loaddata 命令来恢复数据库,但看起来Django在加载这个文件的时候,会先把整个文件都放到内存里,这样就导致内存不够用,程序被强制结束了。

有没有什么办法可以解决这个问题呢?

6 个回答

2

我在把数据从微软的SQL Server迁移到PostgreSQL时遇到了问题,所以我不能使用sqldumppg_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中使用。

3

你可以使用XML格式来进行数据的保存和读取。它在内部是通过文件流来实现的,相比于JSON格式,它占用的内存要少一些。不过,Django在处理JSON数据时并没有使用文件流。

所以你可以试试:

./manage.py dumpdata file.xml

然后再试试

./manage.py loaddata file.xml
3

我想补充一下,我在一个类似的场景中用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作为后端。

7

正如乔所提到的,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。

10

loaddata 通常是用来加载一些小的数据库数据,比如说初始化系统或进行测试,而不是用来处理大量的数据。如果你在使用的时候遇到内存限制,那可能是你没有正确使用这个功能。

如果你还有原来的数据库,建议你使用更合适的工具,比如 PostgreSQL 的 pg_dump 或 MySQL 的 mysqldump

撰写回答