2012-02-24 30 views
3

我有一个Django 1.1应用程序需要每天从一些大json文件导入数据。为了给出一个想法,其中一个文件超过100 Mb,并有90K条目导入Postgresql数据库。在Django中优化Postgresql数据库写入的性能?

我遇到的问题是要导入数据需要很长时间,即按小时数量。我预料到将这些条目写入数据库需要一些时间,但肯定不会那么长,这使我认为我在做一些本质上错误的事情。我已经阅读过类似的stackexchange问​​题,并且提出的解决方案建议使用transaction.commit_manuallytransaction.commit_on_success装饰器分批提交而不是每个.save(),我已经在这样做。

正如我所说,我想知道如果我做错了任何事情(例如批量提交太大?,太多外键?),或者我是否应该从Django模型中退出此功能并直接使用DB API。任何想法或建议?

这里是我处理导入数据时与基本款(我已经删除了一些字段的原代码为简单起见)

class Template(models.Model): 
    template_name = models.TextField(_("Name"), max_length=70) 
    sourcepackage = models.TextField(_("Source package"), max_length=70) 
    translation_domain = models.TextField(_("Domain"), max_length=70) 
    total = models.IntegerField(_("Total")) 
    enabled = models.BooleanField(_("Enabled")) 
    priority = models.IntegerField(_("Priority")) 
    release = models.ForeignKey(Release) 

class Translation(models.Model): 
    release = models.ForeignKey(Release) 
    template = models.ForeignKey(Template) 
    language = models.ForeignKey(Language) 
    translated = models.IntegerField(_("Translated")) 

而这里的代码位的是似乎采取年龄完成:

@transaction.commit_manually 
def add_translations(translation_data, lp_translation): 

    releases = Release.objects.all() 

    # There are 5 releases 
    for release in releases: 

     # translation_data has about 90K entries 
     # this is the part that takes a long time 
     for lp_translation in translation_data: 
      try: 
       language = Language.objects.get(
        code=lp_translation['language']) 
      except Language.DoesNotExist: 
       continue 

      translation = Translation(
       template=Template.objects.get(
          sourcepackage=lp_translation['sourcepackage'], 
          template_name=lp_translation['template_name'], 
          translation_domain=\ 
           lp_translation['translation_domain'], 
          release=release), 
       translated=lp_translation['translated'], 
       language=language, 
       release=release, 
       ) 

      translation.save() 

     # I realize I should commit every n entries 
     transaction.commit() 

     # I've also got another bit of code to fill in some data I'm 
     # not getting from the json files 

     # Add missing templates 
     languages = Language.objects.filter(visible=True) 
     languages_total = len(languages) 

     for language in languages: 
      templates = Template.objects.filter(release=release) 

      for template in templates: 
       try: 
        translation = Translation.objects.get(
            template=template, 
            language=language, 
            release=release) 
       except Translation.DoesNotExist: 
        translation = Translation(template=template, 
               language=language, 
               release=release, 
               translated=0, 
               untranslated=0) 
        translation.save() 

      transaction.commit() 
+0

看看这个最近的答案,这可能有一些有用的通用技巧。 http://stackoverflow.com/questions/9407442/optimise-postgresql-for-fast-testing/9407940#comment11914305_9407940 – 2012-02-24 03:11:00

+0

问题的第二部分被分解为一个[后续问题](http:// stackoverflow。 com/q/9447506/939860) – 2012-03-05 01:36:39

回答

3

经历你的应用程序和处理每一行是很多慢加载数据直接到服务器。即使使用优化的代码。此外,一次插入/更新一行是很多再次比一次处理更慢。

如果导入文件在本地可用于服务器,则可以使用COPY。否则,您可以在标准接口psql中使用元命令\copy。您提到了JSON,为此,您必须将数据转换为合适的平面格式,例如CSV。

如果你只是想将新行添加到表:

COPY tbl FROM '/absolute/path/to/file' FORMAT csv; 

或者,如果你想插入/更新一些行:

第一关:用足够的RAM为temp_buffers(至少暂时,如果可以的话),所以临时表不必写入磁盘。请注意,在访问会话中的任何临时表之前,必须先完成此操作。

SET LOCAL temp_buffers='128MB'; 

内存中的表示法比on.disc数据表示法需要更多的空间。因此,对于100 MB的JSON文件..减去JSON开销,再加上一些Postgres开销,128 MB可能会或可能不够。但你不必去猜测,只是做一个测试运行,并测量它:

select pg_size_pretty(pg_total_relation_size('tmp_x')); 

创建临时表:

CREATE TEMP TABLE tmp_x (id int, val_a int, val_b text); 

或者,只是复制现有的表的结构:

CREATE TEMP TABLE tmp_x AS SELECT * FROM tbl LIMIT 0; 

复制值(应该采取,不小时):

COPY tmp_x FROM '/absolute/path/to/file' FORMAT csv; 

从那里插入/更新与普通的旧SQL。当你正在规划一个复杂的查询,你可能甚至想在临时表中添加指数或两个,并运行ANALYZE:由id

ANALYZE tmp_x; 

例如,更新现有的行,匹配:

UPDATE tbl 
SET col_a = tmp_x.col_a 
USING tmp_x 
WHERE tbl.id = tmp_x.id; 

最后,删除临时表:

DROP TABLE tmp_x; 

还是有它在次自动掉线会议结束。

+0

谢谢,'COPY'好像很有趣!关于一次插入一行,我会认为'@ transaction.commit_manually'会在调用'transaction.commit()'时处理批量插入操作。无论如何,源JSON文件确实可用于服务器,并且我可以轻松将它们转换为CSV,所以我应该可以使用它。 Howerver在阅读了'COPY'文档之后,我不确定如何使用'COPY'和纯SQL来复制我在循环中所做的功能,特别是为模板添加外键值'到'翻译'表格。 – 2012-02-24 06:15:14

+0

我可以这样想:1)使用COPY将所有数据导入临时表2)使用单独的SQL查询将临时表中与非外键有关的数据复制到主'转换'表中,并且在它的时候,找出temp表中的'sourcepackage','template_name','translation_domain'值中的'template'外键,并将相应的'template'对象写入主表。我仍然不确定如何在2)上编写SQL查询,但这听起来像是一个明智的方法吗? – 2012-02-24 06:18:05

+0

是的,它当然可以。应该可以在JOINs的SQL语句中使用。我增加了一些信息来优化性能。 – 2012-02-24 07:00:32