高效迁移大型Django表数据

9 投票
5 回答
6396 浏览
提问于 2025-04-16 19:08

我需要在一个很大的 Django 表中添加一个新列,这个表有500万行数据。我已经有一个 south 的 schemamigration,用来创建这个新列。现在我正在写一个 datamigration 脚本来填充这个新列。代码大概是这样的。(如果你不太了解 south 的迁移,可以忽略掉模型名称前面的 orm.。)

print "Migrating %s articles." % orm.Article.objects.count()
cnt = 0
for article in orm.Article.objects.iterator():            
    if cnt % 500 == 0:
        print "    %s done so far" % cnt
    # article.newfield = calculate_newfield(article)
    article.save()
    cnt += 1

为了减少内存使用,我把 objects.all 换成了 objects.iterator。但是当我运行这个脚本时,还是有东西在消耗大量内存。即使我把实际有用的那一行注释掉,脚本在处理表的时候,内存使用量还是会飙升到10GB以上,然后我就放弃了。

看起来有东西在内存中占用着这些对象。有什么办法可以让这个脚本不那么耗内存吗?

顺便说一下,我使用的是 Python 2.6、Django 1.2.1、South 0.7.2 和 MySQL 5.1。

5 个回答

2

orm.Article.objects.iterator()

这个方法是一次性把所有查询结果都加载到内存里吗?还是说它是每次从数据库里取一行数据?

我猜它是一次性加载所有数据。你可以试着用数据库游标来逐步获取数据,替代这个循环:

比如:http://docs.python.org/library/sqlite3.html#sqlite3.Cursor.fetchmany

db = blah.connect("host='%s' dbname='%s' user='%s' password='%s'" % ...
new, old = db.cursor(), db.cursor()
old.execute("""
    SELECT  *
    FROM    whatever
""")
for row in old.fetchmany(size=500):
    (col1, col2, col3...) = row
    new = db.cursor()
    new.execute("""
        INSERT INTO yourtable (
            col1, col2, col3...)
        VALUES (
            %s, %s, %s, %s, %s)
        """,(col1, col2, col3,...))
new.close()
old.close()

这样做会比较慢。我从我自己的一个独立迁移脚本中提取了这个,所以你的情况可能会有所不同。

fetchmany 是标准的方法(PEP249)。我没有完全实现你想要的功能,所以这个示例还有一些工作要做:我没有在循环中处理数据——也就是没有每次取500条数据直到完成——所以你需要自己解决这个问题。

2

或者,如果你在现场创建一个原始查询,并设置一个基本的结果集大小限制,会发生什么呢?

就像这样: https://docs.djangoproject.com/en/1.3/topics/db/sql/#index-lookups

while min < rowcount:
  min += 500
  max = min + 500
  articles = Article.objects.raw('SELECT * from article where id > %s and id < %s' % (min, max))
  for old_article in articles:
    # create the new article
    article.save()
7

确保 settings.DEBUG 设置为 False。当 DEBUG=True 时,会占用很多内存,特别是在进行数据库操作时,因为它会把所有发送到数据库的查询都存储在一个视图中。

现在有了 Django 1.8 版本,这个问题应该不再那么严重,因为现在最多只会存储 9000 条查询,而之前是可以无限制存储的。

撰写回答