我是否需要多个游标对象来循环记录集并同时更新?

时间:2009-09-22 20:48:02

标签: python database sqlite cursor

所以我有一个大型数据库,我无法立即在内存中保存。我必须循环遍历表中的每个项目,处理它,并将处理后的数据放入表格中的另一列。

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

问题:

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

我是否需要第二次连接数据库才能拥有第二个游标对象,这样我才能做到这一点?

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)

我遇到的问题是我的真正的选择语句中有一个JOIN并且需要一段时间,因此多次执行JOIN非常耗费时间。我试图通过仅执行一次选择来加速该过程,然后使用生成器,因此我不必将其全部保存在内存中。

解决方案:

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

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

我的解决方案是创建一个临时数据库,我使用id粘贴processed_content,这样我每个数据库都有一个连接/游标对象,并且可以继续循环选定的记录集,同时定期插入临时数据库。一旦我到达所选记录集的末尾,我将临时数据库中的数据传回原始数据库。

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

3 个答案:

答案 0 :(得分:3)

我认为你有大致正确的架构 - 用“游标”来表示它会混淆“旧的SQL手”,因为他们会考虑与DECLARE foo CURSOR,{{{{}}相关的许多问题。 1}},FETCH FROM CURSOR和其他与 SQL 游标有关的beauts。 Python DB API的“游标”只是打包和执行SQL语句的一种简便方法,必然与 SQL 游标连接 - 它不会遇到任何这些问题 - 虽然它可能会呈现它(完全原创的)自己的;-)但是,通过你正在做的结果的“批处理”,你正确的提交等,你已经预防性地解决了我所遇到的大部分“原始问题”心。

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

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

答案 1 :(得分:2)

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

Update data_table
set processed_col = Process_Column(col_to_be_processed)

答案 2 :(得分:1)

由于众多原因,游标很糟糕。

我建议你使用单个UPDATE语句而不是去CURSOR路径(并且很多其他人肯定会说这个)。

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

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

根据回复进行编辑:

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