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

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

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

Вопросы:

Будет ли создание второго объекта курсора для запуска операторов обновления, я продолжу цикл над исходным оператором 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)

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

Решение:

Итак, ответ на мои первые два вопроса: "Нет". К моему третьему вопросу, после подключения к базе данных, он блокирует всю базу данных, поэтому другое соединение не сможет ничего сделать до тех пор, пока первое соединение не будет закрыто.

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

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

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

Ответ 1

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

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

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

Ответ 2

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

Update data_table
set processed_col = Process_Column(col_to_be_processed)

Ответ 3

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

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

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

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

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

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