大量记录的bulk_create的最佳实践

时间:2015-09-27 07:55:52

标签: mysql django performance orm

我使用bulk_create将1个mio记录插入到新表中。这需要80秒。 Django只使用一个CPU核心(大约25%的CPU,但没有核心达到100%)我相信有改进潜力。

这是代码

class Stock(models.Model):
    code = models.CharField(max_length=6, unique=True)
    name = models.CharField(max_length=8)

class Watchers(models.Model):
    date = models.DateField(db_index=True)
    stock = models.ForeignKey(Stock, unique_for_date="date")
    num = models.PositiveIntegerField(default=0)

batch = []
for input in inputs:
    watcher = Watcher(date=input['date'], stock=get_stock(), num=input['num'])
    batch.append(watcher)
Watcher.objects.bulk_create(batch)

我尝试了几件事:

  1. 使用正确的batch_size。对我来说最好的价值大约是4000.需要80-90秒。
  2. 使用ThreadPool()。它慢得多,大约120-140秒
  3. 删除DateField上的索引。比1.慢一点。
  4. 我正在使用MySQL 5.6社区版。存储引擎是MyISAM。这是配置。

    [mysqld]
    server-id   = 1
    default-storage-engine = MyISAM
    port        = 3306
    socket      = /tmp/mysql.sock
    datadir     = "{MYSQLDATAPATH}"
    skip-external-locking
    explicit_defaults_for_timestamp = TRUE
    
    # MyISAM #
    key-buffer-size = 32M
    max_allowed_packet = 16M
    
    # CACHES AND LIMITS #
    tmp-table-size                 = 32M
    max-heap-table-size            = 32M
    query-cache-type               = 0
    query-cache-size               = 0
    max-connections                = 500
    thread-cache-size              = 50
    open-files-limit               = 65535
    table-definition-cache         = 1024
    table-open-cache               = 2048
    
    # LOGGING
    log-bin       = mysql-bin
    binlog_format = mixed
    

    当我导入另一个表(类似的结构,相同的索引,但有9列)时,需要15分钟。时间的增加不是线性的。

    bulk_create有什么问题吗?

    更新1

    虽然我接受了答案,但我想我应该理解这个谜。所以我做了一些测试,发现Django的模型创建是减速的根本原因。当我有800000条记录时,调用800000次创建模型将非常耗时。

    ORM框架执行许多我们没有看到的内部工作,例如完整性检查。在我的例子中,大量记录将被导入到空数据库表中,因此不需要进行检查。

    现在我使用cursor.executemany(),它将800,000个4列记录的插入时间从54秒缩短到16秒。并将800000个13列记录的插入时间从13分钟缩短到46秒。

    根据我的实验,您每3000-5000条记录就会调用executemany。我在一次通话中尝试了800k记录,这非常慢。

1 个答案:

答案 0 :(得分:3)

虽然bulk_create对于在处理HTML表单时保存少量记录很有用,但它并不适合保存数千条记录。正如您已经发现的那样,它很慢,因为它需要大量内存并向数据库发送一个非常大的查询。 Fortunatley LOAD DATA IN FILE来救援。

  

LOAD DATA INFILE语句将文本文件中的行读取到   桌子以非常高的速度。 LOAD DATA INFILE是对的补充   SELECT ... INTO OUTFILE。

我们可以生成类似于使用csv writer生成的文件的文件,以下示例来自文档。

import csv
    with open('some.csv', 'wb') as f:
    writer = csv.writer(f)
    writer.writerows(someiterable)

最后,正如您已经发现的那样,有时可以使用LOAD DATA ..的LOCAL选项。

  

LOCAL仅在您的服务器和客户端都已用时才有效   配置为允许它

使用此选项时,无需手动将文件传输到服务器。您可以在客户端生成CSV文件,local选项将使mysql客户端自动将文件传输到服务器。

  

如果未指定LOCAL,则该文件必须位于服务器主机上   并由服务器直接读取。