Итак, у меня есть большая база данных, которую я не могу хранить в памяти сразу. Я должен перебрать каждый элемент в таблице, обработать его и поместить обработанные данные в другой столбец таблицы.
Когда я зацикливаюсь на своем курсоре, если я пытаюсь запустить оператор обновления, он усекает набор записей (я полагаю, потому что он повторно назначает объект курсора).
Вопросы:
Позволит ли создание второго объекта курсора для выполнения операторов обновления продолжить цикл по исходному оператору выбора?
Нужно ли мне второе соединение с базой данных, чтобы иметь второй объект-курсор, что позволит мне сделать это?
Как 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 с идентификатором, чтобы у меня был один объект соединения / курсора на базу данных, и я мог продолжать зацикливать выбранный набор записей, периодически вставляя во временную базу данных. Как только я достигну конца выбранного набора записей, я перенесу данные из временной базы данных обратно в оригинал.
Если кто-нибудь наверняка знает об объектах соединения / курсора, дайте мне знать в комментарии.