Python sqlite3 и параллелизм

65

У меня есть программа Python, которая использует модуль "threading". Раз в секунду моя программа запускает новый поток, который извлекает некоторые данные из Интернета и сохраняет эти данные на моем жестком диске. Я хотел бы использовать sqlite3 для хранения этих результатов, но я не могу заставить его работать. Проблема заключается в следующей строке:

conn = sqlite3.connect("mydatabase.db")
  • Если я помещаю эту строку кода в каждый поток, я получаю OperationalError, говорящий мне, что файл базы данных заблокирован. Я предполагаю, что это означает, что другой поток имеет mydatabase.db через соединение sqlite3 и заблокировал его.
  • Если я поместил эту строку кода в основную программу и передаю объект соединения (conn) для каждого потока, я получаю ProgrammingError, говоря, что объекты SQLite, созданные в потоке, могут использоваться только в том же потоке.

Раньше я сохранял все свои результаты в файлах CSV и не имел ни одной из этих проблем с блокировкой файлов. Надеюсь, это будет возможно с помощью sqlite. Любые идеи?

  • 3
    Я хотел бы отметить, что более поздние версии Python включают в себя более новые версии sqlite3, которые должны решить эту проблему.
  • 0
    @RyanFugger Знаете ли вы, какая самая ранняя версия, которая поддерживает это? Я пользуюсь 2.7
Показать ещё 1 комментарий
Теги:

12 ответов

36
Лучший ответ

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

  • 0
    См. Docs.python.org/library/queue.html.
  • 8
    FWIW: в более поздних версиях sqlite утверждается, что вы можете обмениваться соединениями и объектами между потоками (кроме курсоров), но на практике я нашел иное.
Показать ещё 3 комментария
147

Вопреки распространенному мнению, более новые версии sqlite3 do поддерживают доступ из нескольких потоков.

Это можно включить с помощью необязательного аргумента ключевого слова check_same_thread:

sqlite.connect(":memory:", check_same_thread=False)
  • 4
    Я столкнулся с непредсказуемыми исключениями, и даже Python падает с этой опцией (Python 2.7 в Windows 32).
  • 4
    Согласно документам , в многопоточном режиме ни одно соединение с базой данных не может использоваться в нескольких потоках. Там также сериализованный режим
Показать ещё 7 комментариев
14

Ниже показано на mail.python.org.pipermail.1239789

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

sqlite.connect(":memory:", check_same_thread = False)

отлично работает для меня. Конечно, теперь я должен заботиться безопасного многопоточного доступа к db. В любом случае, все пытаются помочь.

  • 0
    (В случае с GIL истинного многопоточного доступа к БД на самом деле не так много, как я видел)
13

Вы не должны использовать нитки для этого. Это тривиальная задача для twisted, и это, вероятно, значительно повлияет на вас.

Используйте только один поток и завершите запрос, чтобы инициировать событие для записи.

twisted позаботится о планировании, обратных вызовах и т.д. для вас. Он передаст вам весь результат в виде строки, или вы можете запустить его через потоковый процессор (у меня есть twitter API и API-интерфейс friendfeed, который как отключает события для вызывающих, так и результаты по-прежнему загружаются).

В зависимости от того, что вы делаете с вашими данными, вы можете просто скомпилировать полный результат в sqlite по мере его завершения, сварить и свалить, или приготовьте его, пока он читается, и выгрузите его в конце.

У меня очень простое приложение, которое делает что-то близкое к тому, что вы хотите на github. Я называю это pfetch (параллельная выборка). Он захватывает различные страницы по расписанию, передает результаты в файл и, возможно, запускает script после успешного завершения каждого из них. Он также делает некоторые причудливые вещи, такие как условные GET, но все же может быть хорошей основой для того, что вы делаете.

10

Переключитесь на multiprocessing. Это намного лучше, хорошо масштабируется, может выходить за рамки использования нескольких ядер с использованием нескольких процессоров, а интерфейс такой же, как с использованием модуля потоковой передачи python.

Или, как предположил Али, просто используйте механизм объединения потоков SQLAlchemy. Он будет обрабатывать все для вас автоматически и имеет много дополнительных функций, просто чтобы процитировать некоторые из них:

  • SQLAlchemy включает диалекты для SQLite, Postgres, MySQL, Oracle, MS-SQL, Firebird, MaxDB, MS Access, Sybase и Informix; IBM также выпустила драйвер DB2. Поэтому вам не нужно переписывать свое приложение, если вы решите отойти от SQLite.
  • Система Unit Of Work, центральная часть SQLAlchemy Object Relational Mapper (ORM), организует отложенные операции создания/вставки/обновления/удаления в очереди и сбрасывает их все в одну партию. Для этого он выполняет топологическую "сортировку зависимостей" всех измененных элементов в очереди, чтобы соблюдать ограничения внешнего ключа, а также группирует избыточные утверждения вместе, где их иногда можно отбирать еще больше. Это обеспечивает максимальную эффективность и безопасность транзакций и минимизирует вероятность блокировок.
7

Или, если вы ленивы, как и я, вы можете использовать SQLAlchemy. Он будет обрабатывать потоки для вас, (используя поток local и некоторый пул соединений), и как он это делает, даже настраивается.

Для дополнительного бонуса, если/когда вы понимаете/решаете, что использование Sqlite для любого параллельного приложения будет катастрофой, вам не придется менять свой код на использование MySQL или Postgres или что-то еще. Вы можете просто переключиться.

  • 1
    Почему нигде на официальном сайте не указана версия Python?
0

Используйте threading.Lock()

0

Я бы посмотрел на модуль y_serial Python для сохранения данных: http://yserial.sourceforge.net

который обрабатывает проблемы взаимоблокировки, связанные с одной базой данных SQLite. Если спрос на concurrency становится тяжелым, можно легко настроить класс Farm из многих баз данных, чтобы рассеять нагрузку на стохастическое время.

Надеюсь, это поможет вашему проекту... он должен быть достаточно простым, чтобы реализовать через 10 минут.

0

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

0

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

0

Мне нравится ответ Евгения. Очереди - это, как правило, лучший способ реализовать межпоточную связь. Для полноты, вот некоторые другие варианты:

  • Закройте соединение с БД, когда порожденные потоки завершили его использование. Это исправит ваш OperationalError, но открытие и закрытие соединений, как это, как правило, No-No из-за служебных накладных расходов.
  • Не используйте дочерние потоки. Если задание раз в секунду достаточно легкое, вы можете уйти с выполнением выборки и сохранения, а затем спать до нужного момента. Это нежелательно, так как операции выборки и хранения могут занимать > 1сек, и вы теряете преимущества мультиплексированных ресурсов, которые вы используете с многопоточным подходом.
-1

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

conn.commit()

после завершения операции с базой данных. Если вы этого не сделаете, ваша база данных будет заблокирована для записи и останется такой. Другие потоки, которые ждут записи, будут тайм-аутом через некоторое время (по умолчанию установлено значение 5 секунд, см. http://docs.python.org/2/library/sqlite3.html#sqlite3.connect для получения подробной информации об этом).

Примером правильной и параллельной вставки будет следующее:

import threading, sqlite3
class InsertionThread(threading.Thread):

    def __init__(self, number):
        super(InsertionThread, self).__init__()
        self.number = number

    def run(self):
        conn = sqlite3.connect('yourdb.db', timeout=5)
        conn.execute('CREATE TABLE IF NOT EXISTS threadcount (threadnum, count);')
        conn.commit()

        for i in range(1000):
            conn.execute("INSERT INTO threadcount VALUES (?, ?);", (self.number, i))
            conn.commit()

# create as many of these as you wish
# but be careful to set the timeout value appropriately: thread switching in
# python takes some time
for i in range(2):
    t = InsertionThread(i)
    t.start()

Если вам нравится SQLite или другие инструменты, которые работают с базами данных SQLite, или вы хотите заменить CSV файлы файлами SQLite db или должны делать что-то редкое, как межплатформенный IPC, то SQLite - отличный инструмент и очень подходит для цель. Не позволяйте себе оказывать давление на использование другого решения, если оно не кажется правильным!

Ещё вопросы

Сообщество Overcoder
Наверх
Меню