遍历非常大的表并更新行的最有效方法是什么?

时间:2019-04-05 16:04:42

标签: python sqlite

问题:

在Python(3.6)中使用内置的SQLite3数据库时,对一个非常大的数据库表进行迭代并逐行更新的注意事项是什么?

要求:

我将需要迭代每一行,并使用列中的信息来执行操作,然后使用结果更新该行的第二列。我无法即时执行此操作,我需要完全更新表格。

此外,根据数据库的大小以及执行每个操作所需的时间,此操作预计将持续多天。考虑到这一点,它必须具有容错能力,能够定期提交更改。

问题

我首先要研究不同的提取方法,但我想知道不同方法对内存的考虑。我见过另一台服务器上的外部数据库,当您执行fetchmany()时,数据库会生成带有所有结果的select语句,但一次仅返回arraysize个结果-是这种情况与SQLite以及?如果是这样,使用fetchmany() vs fetchall() vs fetchone()有什么好处吗?

我计划与executemany()一起使用生成器函数,以便更新数据库并定期提交,假设我可以分块获取以便间歇性中断生成器函数以使其提交。

示例:

table:
  columns: id, value1, updated_value

def action_function():
    next_id = next_row()
    updated_value = compute_value(id)
    yield (updated_value, next_id)

def next_row():
    while True:
        results = cur.fetchmany(arraysize)
        if not results:
            break
        for result in results:
            yield result[0]

cur.executemany('''UPDATE table SET updated_value = ? WHERE id = ?''', action_function())

这应该是什么样子吗?我认为可能应该颠倒函数,以使分块提交每个arraysize

最后,数据库有数百万行,需要计算每行一个操作(使用同一行中的值),然后更新一列。将数据库加载到内存可能无法正常工作,因此fetchall()似乎很不错,但是考虑到标准SQL数据库在SELECT语句将结果加载到内存的情况下如何工作,Python中的SQLite3会发生这种情况吗?如果没有,那是怎么回事?

是否应该有更好的方法,例如简单地为每个块保留start_rowid和stop_rowid的变量,然后计算下一个块的大小,并为这些索引执行SELECT语句?

感谢您的帮助!

编辑:

作为替代选择,有create_function逐行显示。

类似这样的东西:

con.create_function("action", 1, compute_value)
cur.execute("UPDATE table SET updated_value = action(id)")

This answer建议使用此方法,但考虑到它至少必须具有一定的容错能力,所以我认为该方法不会起作用,并且仅由于以下原因,完成时间仍然很大: compute_value做事情的时间。

如果我使用此方法并且过程被中断,那么到目前为止计算出的值会被提交到数据库还是会丢失?

3 个答案:

答案 0 :(得分:1)

假设无法在数据库上完成compute_value(),我将使其保持简单:

SELECT-全部。然后fetchone(),运行compute_value()UPDATE那一行。您的问题听起来很像compute_value(),这是您大部分时间都在输的地方。一次读取并提交一行可能不会增加太多开销,并且将对内存的影响最小化。它还带来了额外的好处,即您总是在每个compute_value()之后提交。如果您认为这样,我将使用fetchmany()切换到窗口化方法。

研究如何在行基础上并行执行compute_value()可能对您而言很有意义。将multiprocessing与您的行是作业的作业队列一起使用会很有益。

答案 1 :(得分:1)

最好分批执行任务:

  1. 读取您需要更新的所有行(即2000行)
  2. 在内存中进行计算(使用熊猫或其他方法)
  3. 通过executemany批量更新数据库中的所有这些行

它将大大减少IO进出数据库的时间

答案 2 :(得分:1)

  

我首先要查看不同的提取方法,但我想知道   不同方法对内存的考虑是什么。我有   当您执行其他操作时,可以看到另一台服务器上的外部数据库   fetchmany()数据库正在生成所有的select语句   您的结果,但一次仅返回arraysize结果-是   SQLite也是如此吗?如果是这样,有什么好处吗?   使用fetchmany()vs fetchall()vs fetchone()吗?

首先,SQLite Docs讨论查询在SQLite后端的工作方式。概括起来,一旦启动SELECT语句,就准备好sqlite3_stmt。此处包含检索结果的说明。要获得结果的下一行,将调用sqlite3_step()直到下一个结果行准备就绪。

因此可以确定,在Python界面上,当您执行fetchone()时,它将运行step()一次。当您执行fetchmany()时,它会循环遍历step()直到结果量等于您的数组大小属性,然后将它们放入Python列表对象中。 fetchall()一直循环播放,直到没有结果可言为止,并再次使它们成为Python列表对象。因此,内存是有好处的,因为基于结果的数量,结果列表对象的大小将有所不同。您的光标对象将始终为静态大小。


  

有没有更好的方法,例如简单地按住   每个块的start_rowid和stop_rowid变量,然后   计算下一个块的大小,并只为   那些索引?

执行此方法将花费更多时间来关闭语句,然后准备下一条语句,而不是执行一次SELECT然后一次使用fetchmany()来检索数据块< / p>


就本示例的其余部分而言,我决定采用一种多处理方法(由Ente建议),以充分利用资源,并将与SELECT一起使用单个fetchone()添加到队列中。我将有多个工作进程从Queue中提取并发送API调用,然后将结果添加到DoneQueue中。我将通过DoneQueue进行最后的过程分块,然后对executemany()进行UPDATE