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