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