13

因此,我有一个无法立即保存在内存中的大型数据库。我必须遍历表中的每个项目,对其进行处理,然后将处理后的数据放入表中的另一列。

当我在游标上循环时,如果我尝试运行更新语句,它会截断记录集(我相信是因为它重新利用了游标对象)。

问题:

创建第二个游标对象来运行更新语句是否允许我继续循环原始选择语句?

我是否需要第二个与数据库的连接才能拥有第二个游标对象,这将允许我这样做吗?

sqlite 将如何响应与数据库的两个连接,一个从表中读取,另一个向它写入?

我的代码(简化):

import sqlite3

class DataManager():
    """ Manages database (used below). 
        I cut this class way down to avoid confusion in the question.
    """
    def __init__(self, db_path):
        self.connection = sqlite3.connect(db_path)
        self.connection.text_factory = str
        self.cursor = self.connection.cursor()

    def genRecordset(self, str_sql, subs=tuple()):
        """ Generate records as tuples, for str_sql.
        """
        self.cursor.execute(str_sql, subs)
        for row in self.cursor:
            yield row

select = """
            SELECT id, unprocessed_content 
            FROM data_table 
            WHERE processed_content IS NULL
         """

update = """
            UPDATE data_table
            SET processed_content = ?
            WHERE id = ?
         """
data_manager = DataManager(r'C:\myDatabase.db')
subs = []
for row in data_manager.genRecordset(str_sql):
    id, unprocessed_content = row
    processed_content = processContent(unprocessed_content)
    subs.append((processed_content, id))

    #every n records update the database (whenever I run out of memory)
    if len(subs) >= 1000:
        data_manager.cursor.executemany(update, subs)
        data_manager.connection.commit()
        subs = []
#update remaining records
if subs:
    data_manager.cursor.executemany(update, subs)
    data_manager.connection.commit()

我尝试的另一种方法是将我的 select 语句修改为:

select = """
            SELECT id, unprocessed_content 
            FROM data_table 
            WHERE processed_content IS NULL
            LIMIT 1000
         """

然后我会这样做:

recordset = data_manager.cursor.execute(select)
while recordset:
    #do update stuff...
    recordset = data_manager.cursor.execute(select)

我遇到的问题是我真正的select 语句中有一个 JOIN 并且需要一段时间,因此多次执行 JOIN 非常耗时。我试图通过只做一次选择来加快这个过程,然后使用一个生成器,这样我就不必把它全部保存在内存中。

解决方案:

好的,所以我的前两个问题的答案是“否”。对于我的第三个问题,一旦与数据库建立连接,它就会锁定整个数据库,因此在第一个连接关闭之前,另一个连接将无法执行任何操作。

我找不到它的源代码,但从经验证据来看,我相信连接一次只能使用一个游标对象,并且最后一次运行查询优先。这意味着,当我循环选择的记录集一次产生一行时,一旦我运行我的第一个更新语句,我的生成器就会停止产生行。

我的解决方案是创建一个临时数据库,我将已处理的内容与 id 一起插入,以便每个数据库都有一个连接/光标对象,并且可以继续循环遍历选定的记录集,同时定期插入临时数据库。一旦我到达我选择的记录集的末尾,我就会将临时数据库中的数据传输回原始数据库。

如果有人确切知道连接/光标对象,请在评论中告诉我。

4

3 回答 3

3

我认为你有大致正确的架构——用“游标”来表示它会混淆“老 SQL 手”,因为他们会考虑与DECLARE foo CURSOR, FETCH FROM CURSOR,WHERE CURRENT OF CURSOR以及其他与SQL游标。Python DB API 的“游标”只是一种打包和执行 SQL 语句的便捷方式,不一定SQL游标相关联——它不会遇到任何这些问题——尽管它可能会出现(完全原创的)自己的问题; -) 但是,通过你正在做的结果的“批处理”,你的正确提交等,你已经预防性地解决了我想到的大多数“原始问题”。

在其他一些引擎上,我建议先选择一个临时表,然后在更新主表时从该临时表中读取,但我不确定在 sqlite 中性能将如何受到影响,具体取决于您拥有的索引(如果您的更新没有影响索引,那么我怀疑这样的临时表在 sqlite 中根本不是优化——但我无法对您的数据运行基准测试,这是检查性能假设的唯一真正方法)。

所以,我想说,去吧!-)

于 2009-09-23T04:16:26.977 回答
2

是否可以创建一个数据库函数来处理您的内容?如果是这样,您应该能够编写一个更新语句并让数据库完成所有工作。例如;

Update data_table
set processed_col = Process_Column(col_to_be_processed)
于 2009-09-24T19:14:56.257 回答
1

由于多种原因,光标很糟糕。

我建议(并且许多其他人肯定会加入)您使用单个 UPDATE 语句而不是使用 CURSOR 路线。

您的 Processed_Content 是否可以作为参数发送到执行基于集合的操作的单个查询,如下所示:

UPDATE data_table
SET processed_content = ?
WHERE processed_content IS NULL
LIMIT 1000

根据回复编辑:

由于每一行都有唯一的 Processed_Content 值,因此您别无选择,只能使用记录集和循环。我过去曾多次这样做过。您的建议应该有效。

于 2009-09-22T20:55:13.857 回答