Django装载fixture非常缓慢。

4

我试图使用两组夹具提供初始数据。第一组夹具格式如下。

  {
    "pk": 1,
    "model": "data.Person",
    "fields": {
      "full": "Anna-Varney",
      "num": "I",
      "short": "Anna-Varney"
    }
  },

我首先将它加载进去,大约需要1-2个小时才能成功加载。我的电影.json格式如下:

  {
    "pk": 1,
    "model": "data.Film",
    "fields": {
      "date": "2005-08-01",
      "rating": 8.3,
      "actors": [
        [
          "Anna-Varney"
        ]
      ],
      "name": "Like a Corpse Standing in Desperation (2005) (V)"
    }
  },

加载电影夹具花费了极长的时间,目前已经进行了20个小时,我的计算机在运行时变得缓慢。两个月前,我加载了类似的固定装置,除了我使用MySQL(现在我正在使用Postgres),并且我已经在我的模型中添加了日期字段。过去,当我将电影固件加载到旧的MySQL数据库中时,只需要2-3个小时。是否有一种方法可以确定固件加载部分所处的步骤或是否已冻结?
参考我的模型:
class PersonManager(models.Manager):
    def get_by_natural_key(self, full):
        return self.get(full=full)

class Person(models.Model):
    objects = PersonManager()
    full = models.CharField(max_length=100,unique = True)
    short = models.CharField(max_length=100)
    num = models.CharField(max_length=5)
    def natural_key(self):
        return (self.full,)

    def __unicode__(self):
        return self.full


class Film(models.Model):
    name = models.TextField()
    date = models.DateField()
    rating = models.DecimalField(max_digits=3 , decimal_places=1)
    actors = models.ManyToManyField('Person')

    def __unicode__(self):
        return self.name

我会尝试的第一件事是在full上创建索引。自然键处理必须为每部电影找到正确的“Person”实例,因此它一遍又一遍地在full上搜索。 - Peter DeGlopper
好的,我会尝试一下。如果我的应用程序大量使用 Twitter 的 typeahead.js,并且视图调用了一个 istartswith 来搜索短语,那么我是否也应该将短语作为索引? - dl8
我不确定 - 这取决于istartswith查询如何被发送到Postgres。 - Peter DeGlopper
3个回答

2
如果您正在通过命令行加载夹具:
python manage.py loaddata --database=MY_DB_LABEL fixtures/my_fixture.json;

或者通过 shell 编程实现:
os.system('python manage.py loaddata --database=%s fixtures/my_fixture.json;' % MY_DB_LABEL)

装载测试数据将会很。(我没有调查原因,可能是由于存在许多不必要的中间数据库保存操作。)


解决方案:通过Python以单个事务的方式编程式加载您的固定装置。
from django.db import transaction
from django.core.management import call_command

with transaction.atomic(using=MY_DB_LABEL):
    call_command('loaddata', 'fixtures/my_fixture.json', database=MY_DB_LABEL)
    call_command('loaddata', 'fixtures/my_other_fixture.json', database=MY_DB_LABEL)

装载夹具将会极大地加速DRAMATICALLY


请注意,这里的databaseusing参数是可选的。如果您只使用一个数据库,则它们是不必要的。但如果像我一样使用多个数据库,您可能希望使用它来确保将装置数据加载到哪个数据库中。

1
有趣的方法,感谢分享。不幸的是,它并没有加速我的事情... - Matthias Güntert

2

对于大多数情况,您可以通过编程方式加载已转储的数据并使用bulk_create来大大加快速度。

例如:

from collections import defaultdict
from django.core import serializers                                                                     

obj_dict = defaultdict(list)
deserialized = serializers.deserialize('json', open('my_fixtures.json'))
# organize by model class
for item in deserialized:
  obj = item.object
  obj_dict[obj.__class__].append(obj) 

for cls, objs in obj_dict.items():
  cls.objects.bulk_create(objs)

这肯定加快了事情的进展,但我注意到许多对多关系没有被转移过来。似乎在反序列化时,被表示为数组的多对多关系变成了null。有什么想法吗? - kyldu

1
因为Django在自动提交模式下运行,它要求数据库确认每个对象创建后都会立即保存并在物理位置上的硬盘上进行同步。这限制了保存对象的数量与硬盘读写速度相同。
你需要使用@transaction.atomic装饰器或with transaction.atomic()上下文管理器,以允许数据库确保一切只被安全地保存一次 - 在最后。
你可以阅读Django文档中有关事务的更多信息
我甚至建议在使用Django时将ATOMIC_REQUESTS设置为True,特别是在与PostgreSQL一起使用时。这样,每个浏览器请求都将自动在一个事务中处理,并且仅在结果视图成功运行时才提交。

1
我应该把这些应用在哪里呢?交易文档主要将其应用于视图,但我的问题是首先尝试通过固定装置将数据加载到数据库中。 - dl8
我不是很了解Django,但我认为fixtures并不适用于批量数据加载。我认为它们只是像我描述的那样一个一个地保存对象。也许在数据库配置中将ATOMIC_REQUESTS设置为True可以使它们在一个事务中运行,并加快速度。 - Tometzky
1
我大约一个月前添加了大致相同数量的数据,只不过我使用的是MySQL和略微不同的模型。装置件加载得很好。由于某种原因,改用Postgres并微调模型会使其加载非常缓慢(或在加载时冻结)。而且我认为ATOMIC_REQUESTS不会有任何作用,因为它与HTTP请求绑定在一起,而我现在没有处理任何这方面的内容,我只是试图将初始数据加载到我的数据库中。 - dl8
如果您可以在 postgresql.conf 中更改Postgres参数,则可以尝试将synchronous_commit选项设置为off并重新启动数据库。这样做会冒失失去一些事务(默认情况下约1秒钟)在断电的情况下,但提交速度会更快。 - Tometzky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接