Нужно ли несколько объектов курсора для циклического перебора набора записей и обновления одновременно? - PullRequest
11 голосов
/ 23 сентября 2009

Итак, у меня есть большая база данных, которую я не могу хранить в памяти сразу. Я должен перебрать каждый элемент в таблице, обработать его и поместить обработанные данные в другой столбец таблицы.

Когда я зацикливаюсь на своем курсоре, если я пытаюсь запустить оператор обновления, он усекает набор записей (я полагаю, потому что он повторно назначает объект курсора).

Вопросы:

Позволит ли создание второго объекта курсора для выполнения операторов обновления продолжить цикл по исходному оператору выбора?

Нужно ли мне второе соединение с базой данных, чтобы иметь второй объект-курсор, что позволит мне сделать это?

Как 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 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)

Проблема, с которой я столкнулся, заключалась в том, что мой оператор real select содержит JOIN и занимает некоторое время, поэтому многократное выполнение JOIN занимает очень много времени. Я пытаюсь ускорить процесс, делая выбор только один раз, затем используя генератор, поэтому мне не нужно хранить все это в памяти.

Решение:

Хорошо, поэтому ответ на мои первые два вопроса - «Нет». На мой третий вопрос, после установления соединения с базой данных она блокирует всю базу данных, поэтому другое соединение не сможет ничего сделать, пока не будет закрыто первое соединение.

Я не смог найти исходный код для него, но из эмпирических данных я считаю, что соединение может использовать только один объект-курсор за раз, и последний запрос запуска имеет приоритет. Это означает, что, хотя я зацикливаюсь на выбранном наборе записей, дающем по одной строке за раз, как только я запускаю свой первый оператор обновления, мой генератор перестает давать строки.

Мое решение состоит в том, чтобы создать временную базу данных, в которую я добавляю process_content с идентификатором, чтобы у меня был один объект соединения / курсора на базу данных, и я мог продолжать зацикливать выбранный набор записей, периодически вставляя во временную базу данных. Как только я достигну конца выбранного набора записей, я перенесу данные из временной базы данных обратно в оригинал.

Если кто-нибудь наверняка знает об объектах соединения / курсора, дайте мне знать в комментарии.

Ответы [ 3 ]

3 голосов
/ 23 сентября 2009

Я думаю, что вы имеете примерно правильную архитектуру - представление ее в виде «курсоров» приведет в замешательство «старые руки SQL», потому что они будут думать о многих проблемах, связанных с DECLARE foo CURSOR, FETCH FROM CURSOR, WHERE CURRENT OF CURSOR и другие подобные beauts, связанные с SQL курсорами. «Курсор» Python DB API - это просто удобный способ упаковать и выполнить операторы SQL, не , обязательно связанный с курсорами SQL - он не будет страдать ни от одной из этих проблем - хотя он может представлять свои (совершенно оригинальные) собственные ;-) Но, с учетом "пакетирования" результатов, которые вы делаете, ваших правильных коммитов и т. д., вы превентивно оттачивали большинство тех "оригинальных проблем", которые я имел в виду.

На некоторых других движках я бы предложил сначала сделать выбор во временную таблицу, а затем читать из этой временной таблицы при обновлении основной, но я не уверен, как это повлияет на производительность в sqlite, в зависимости от того, какие индексы у вас есть (если обновление не влияет на индекс, то я подозреваю, что такая временная таблица вообще не будет оптимизацией в sqlite - но я не могу запустить тесты для ваших данных, единственный реальный способ проверить гипотезы производительности ).

Итак, я бы сказал, пойти на это! -)

2 голосов
/ 24 сентября 2009

Можно ли создать функцию БД, которая будет обрабатывать ваш контент? Если это так, вы сможете написать один оператор обновления и позволить базе данных выполнять всю работу. Например,

Update data_table
set processed_col = Process_Column(col_to_be_processed)
1 голос
/ 23 сентября 2009

Курсоры плохо плохо плохо по множеству причин.

Я бы посоветовал (и многие другие обязательно включатся), чтобы вы использовали один оператор UPDATE вместо того, чтобы идти по маршруту CURSOR.

Может ли ваш Processed_Content быть отправлен в качестве параметра для одного запроса, который выполняет операции на основе, например:

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

Отредактировано на основе ответов:

Поскольку каждая строка имеет уникальное значение для Processed_Content, у вас нет другого выбора, кроме как использовать набор записей и цикл. Я делал это в прошлом несколько раз. То, что вы предлагаете, должно работать эффективно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...