【问题标题】:Optimizing a code to populate database using python (Django)优化代码以使用 python (Django) 填充数据库
【发布时间】:2015-12-31 21:20:25
【问题描述】:

我正在尝试使用 Django 使用包含 600 万条记录的文件中的数据填充 SQLite 数据库。然而,即使有 50000 条记录,我编写的代码也会给我带来很多时间问题。

这是我试图填充数据库的代码:

import os

def populate():   
    with open("filename") as f:
        for line in f:
            col = line.strip().split("|")
            duns=col[1]
            name=col[8]
            job=col[12]        

            dun_add = add_c_duns(duns)   
            add_contact(c_duns = dun_add, fn=name, job=job)

def add_contact(c_duns, fn, job):
    c = Contact.objects.get_or_create(duns=c_duns, fullName=fn, title=job)
    return c

def add_c_duns(duns):
    cd = Contact_DUNS.objects.get_or_create(duns=duns)[0]
    return cd  

if __name__ == '__main__':
    print "Populating Contact db...."
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
    from web.models import Contact, Contact_DUNS
    populate()
    print "Done!!"

代码运行良好,因为我已经使用虚拟记录对此进行了测试,并且它给出了预期的结果。我想知道是否有一种方法可以降低此代码的执行时间。谢谢。

【问题讨论】:

  • ORM 很少擅长批量操作(任何语言)。他们需要在内存中创建对象、跟踪状态等……他们还倾向于低效地使用连接,而不是优化/批处理多个操作。更糟糕的是,您要求 ORM 在选择操作(插入/更新)之前检查对象是否存在,这会增加每条记录的开销。根据您使用的数据库引擎,查看适当的批量导入工具或(至少)生成原始 SQL 语句。

标签: python django performance


【解决方案1】:

我没有足够的声誉来发表评论,但这是一个推测性的答案。

基本上,通过 django 的 ORM 执行此操作的唯一方法是使用 bulk_create 。所以首先要考虑的是get_or_create的使用。如果您的数据库现有记录可能在输入文件中有重复,那么您唯一的选择就是自己编写 SQL。如果您使用它来避免重复inside输入文件,然后对其进行预处理以删除重复行。

所以如果你可以不用get_or_create的get部分,那么你可以遵循这个策略:

  1. 遍历输入文件的每一行并为每个条目实例化一个 Contact_DUNS 实例(实际上并不创建行,只需写入 Contact_DUNS(duns=duns) )并将所有实例保存到一个数组中。将数组传递给bulk_create 以实际创建行。

  2. 使用value_list 生成一个DUNS-id 对列表,并将它们转换为dict,其中DUNS 编号为键,行ID 为值。

  3. 重复步骤 1,但使用联系人实例。在创建每个实例之前,使用 DUNS 编号从步骤 2 的字典中获取 Contact_DUNS id。按以下方式实例化每个联系人:Contact(duns_id=c_duns_id, fullName=fn, title=job)。同样,收集联系人实例后,只需将它们传递给 bulk_create 即可创建行。

这应该会从根本上提高性能,因为您将不再为每个输入行执行查询。但正如我上面所说,这只有在您可以确定数据库或输入文件中没有重复项时才有效。

编辑代码如下:

import os

def populate_duns():
    # Will only work if there are no DUNS duplicates
    # (both in the DB and within the file)
    duns_instances = []   
    with open("filename") as f:
        for line in f:
            duns = line.strip().split("|")[1]        
            duns_instances.append(Contact_DUNS(duns=duns))

    # Run a single INSERT query for all DUNS instances
    # (actually it will be run in batches run but it's still quite fast)
    Contact_DUNS.objects.bulk_create(duns_instances)

def get_duns_dict():
    # This is basically a SELECT query for these two fields
    duns_id_pairs = Contact_DUNS.objects.values_list('duns', 'id')
    return dict(duns_id_pairs)

def populate_contacts():
    # Repeat the same process for Contacts
    contact_instances = []
    duns_dict = get_duns_dict()

    with open("filename") as f:
        for line in f:  
            col = line.strip().split("|")
            duns = col[1]
            name = col[8]
            job = col[12]

            ci = Contact(duns_id=duns_dict[duns],
                         fullName=name,
                         title=job)
            contact_instances.append(ci)

    # Again, run only a single INSERT query
    Contact.objects.bulk_create(contact_instances)

if __name__ == '__main__':
    print "Populating Contact db...."
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
    from web.models import Contact, Contact_DUNS
    populate_duns()
    populate_contacts()
    print "Done!!"

【讨论】:

  • 我目前正在尝试使用 bulk_create() 和 raw(),仍在编​​写代码,如果您能帮助我解决 bulk_create,我将不胜感激!!!
【解决方案2】:

CSV 导入

首先,600 万条记录对于 sqllite 来说是相当多的,更糟糕​​的是 sqlite isn't very good 和直接导入 CSV 数据。

对于 CSV 文件的外观没有标准,并且 SQLite shell 甚至没有尝试处理所有复杂的 解释 CSV 文件。如果您需要导入复杂的 CSV 文件并 SQLite shell 不能处理它,你可能想尝试不同的 前端,如 SQLite 数据库浏览器。

另一方面,Mysql 和 Postgresql 处理 CSV 数据的能力更强,而 mysql 的 LOAD DATA IN FILE 和 Postgresql COPY 都是在很短的时间内导入大量数据的轻松方式。

Sqlite 的适用性。

您正在使用 django => 您正在构建一个 Web 应用程序 => 多个用户将访问数据库。这是关于concurrency的手册。

SQLite 支持无限数量的同时阅读器,但它 任何时候都只允许一位作家。对于很多 情况下,这不是问题。作家排队。每个应用程序 它的数据库是否快速工作并继续前进,并且没有锁定持续 超过几十毫秒。但是有一些应用 需要更多的并发性,并且这些应用程序可能需要寻求 不同的解决方案。

甚至您的读取操作也可能相当慢,因为 sqlite 数据库只是一个文件。所以有了这么多的数据,就会涉及到很多的查找操作。数据不能像使用适当的客户端服务器数据库那样分布在多个文件甚至磁盘上。

对您来说好消息是,使用 Django,您通常只需更改 settings.py 即可从 Sqlite 切换到 Mysql 再到 Postgresql。无需进行其他更改。 (反过来并不总是正确的)

所以我敦促您在深入了解之前考虑切换到 mysql 或 postgresl。它将帮助您解决当前的问题,也有助于避免您迟早会遇到的问题。

【讨论】:

    【解决方案3】:

    通过 Python 导入 6,000,000 是相当多的。如果 Python 不是硬性要求,您可以编写一个 SQLite 脚本直接import the CSV data 并使用 SQL 语句创建您的表。更快的是使用 awk 预处理您的文件并输出与您的两个表相对应的两个 CSV 文件。

    我以前使用 sqlite3 CSV 导入器导入 20,000,000 条记录,只用了几分钟。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-04-19
      • 2011-04-15
      • 2021-09-20
      • 2017-10-24
      • 1970-01-01
      • 2021-02-01
      • 1970-01-01
      • 2017-05-20
      相关资源
      最近更新 更多