2009-11-05 3 views
35

Я разрабатываю приложение с SQLite как базу данных, и у меня есть небольшая проблема с пониманием того, как использовать его в нескольких потоках (к сожалению, мне не помогли ни один из других вопросов переполнения стека).Как использовать SQLite в многопоточном приложении?

Мой прецедент: в базе данных есть одна таблица, назовем ее «A», которая имеет разные группы строк (на основе одного из их столбцов). У меня есть «основной поток» приложения, который читает содержимое из таблицы A. Кроме того, я иногда решает обновить определенную группу строк. Для этого я хочу создать новый поток, удалить все строки группы и повторно вставить их (это единственный способ сделать это в контексте моего приложения). Это может произойти с разными группами одновременно, поэтому у меня может быть 2+ потока, пытающихся обновить базу данных.

Я использую разные транзакции из каждой темы, I.E. в начале цикла обновления каждого потока у меня есть начало. Фактически, каждый поток фактически выполняет вызов «BEGIN», удаляет из базы данных все строки, необходимые для «обновления», и вставляет их снова с новыми значениями (это так, как это должно быть сделано в контексте моего заявление).

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

  1. Нужно ли мне называть «открывать» и создавать новую структуру sqlite из каждого потока?
  2. Должен ли я добавить какой-либо специальный код для всего этого или достаточно, чтобы порождать разные потоки, обновлять строки, и это нормально (поскольку я использую разные транзакции)?
  3. Я видел что-то, говоря о разных типах блокировок, и о том, что я мог бы получить «SQLite занят» от вызова определенных API, но, честно говоря, я не видел ссылок, которые полностью объяснялись, когда мне нужно было все это в учетную запись. Нужно ли мне?

Если кто-нибудь может ответить на вопросы/указать мне в сторону хорошего ресурса, я был бы очень благодарен.

ОБНОВЛЕНИЕ 1: Из всего, что я читал до сих пор, похоже, что у вас не может быть двух потоков, которые все равно будут писать в файл базы данных.

См.: http://www.sqlite.org/lockingv3.html. В разделе 3.0: ЗАБРОНИРОВАННАЯ блокировка означает, что процесс планирует записать в файл базы данных в какой-то момент в будущем, но в настоящее время он просто считывает из файла. За один раз может быть активен только один замок RESERVED, хотя несколько замков SHARED могут сосуществовать с одной блокировкой RESERVED.

Означает ли это, что я могу также создавать только один поток, чтобы обновлять группу строк каждый раз? У меня есть какой-то поток poller, который решает, что мне нужно обновить некоторые из строк, а затем создает новый поток для этого, но не более одного за раз? Так как это похоже на то, что любой другой поток, который я создаю, просто получит SQLITE_BUSY до тех пор, пока первый поток не закончится.

Правильно ли я понял вещи?

Кстати, спасибо за ответы, они помогли много.

+0

Не могли бы вы уточнить, почему вы должны УДАЛИТЬ строки и вставлять их вместо того, чтобы просто делать ОБНОВЛЕНИЕ? Я спрашиваю, потому что это похоже на окольный способ делать вещи, и может быть более простое решение. – Wernsey

+0

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

+1

Как идея, намного быстрее просто отбросить стол и воссоздать его, если вы планируете каждый раз удалять все строки. – Snazzer

ответ

21

Заканчивать this link. Самый простой способ - сделать блокировку самостоятельно и не допускать совместного использования потоков между потоками. Еще один хороший ресурс может быть найден here, и он заключает с:

  1. Убедитесь, что вы собираете SQLite с -DTHREADSAFE = 1.

  2. Убедитесь, что каждый поток открывает файл базы данных и сохраняет свою собственную структуру sqlite.

  3. Убедитесь, что вы обрабатываете вероятную возможность столкновения одного или нескольких потоков при одновременном доступе к файлу db: соответствующим образом обрабатывайте SQLITE_BUSY.

  4. Убедитесь, что вы заключили в транзакции команды, которые изменяют файл базы данных, например INSERT, UPDATE, DELETE и другие.

+0

Что именно вы подразумеваете под «блокировкой себя»? И.Е., просто убедитесь, что ни один из двух потоков не пишет в БД сразу? Если да, могу ли я разрешить двум различным потокам читать БД одновременно, не сталкиваясь с какими-либо проблемами? –

+3

«Сделайте блокировку самостоятельно»: убедитесь, что потоки не читаются и не записываются в базу данных одновременно, блокируя мьютексы (которые они должны разделить, конечно). Я хотел сказать, что было бы проще сделать это самостоятельно с помощью библиотеки, которую вы знаете и понимаете, чем полагаться на менее понятную семантику блокировки Sqlite (которая снова зависит от флагов, с которыми вы их скомпилировали). – Kristian

+0

Вторая ссылка была большой, спасибо большое. –

25

Некоторых шагов при запуске с SQLlite для многопоточного использования:

  1. Убедитесь, что SQLite скомпилирована с многопоточным флагом.
  2. Чтобы создать соединение в каждом потоке, вы должны открыть его в своем файле sqlite, не разделяя связи между потоками.
  3. SQLite имеет очень консервативную модель потоковой передачи, когда вы выполняете операцию записи, которая включает операции открытия, которые собираются выполнить INSERT/UPDATE/DELETE, другие потоки будут заблокированы до завершения этой операции.
  4. Если вы не используете транзакцию, транзакции неявны, поэтому, если вы запустите INSERT/DELETE/UPDATE, sqlite попытается получить эксклюзивную блокировку и завершить операцию перед ее выпуском.
  5. Если вы выполняете инструкцию BEGIN EXCLUSIVE, она будет приобретать эксклюзивный замок перед выполнением операций в этой транзакции. COMMIT или ROLLBACK освободят блокировку.
  6. Ваши sqlite3_step, sqlite3_prepare и некоторые другие вызовы могут возвращать SQLITE_BUSY или SQLITE_LOCKED. SQLITE_BUSY обычно означает, что sqlite необходимо получить блокировку. Самая большая разница между двумя возвращаемыми значениями:
    • SQLITE_LOCKED: если вы получаете это из инструкции sqlite3_step, вы ДОЛЖНЫ вызывать sqlite3_reset в дескрипторе инструкции. Вы должны получить это только при первом вызове sqlite3_step, поэтому после вызова сброса вы можете «повторить» ваш вызов sqlite3_step. В других операциях это то же самое, что и SQLITE_BUSY
    • SQLITE_BUSY: нет необходимости вызывать sqlite3_reset, просто повторите операцию после того, как вы немного задержались, чтобы блокировка была выпущена.
+1

SQLITE_LOCKED не имеет ничего общего с блокировкой mutltithreaded. Обычно это ошибка программирования, которую вы получаете при вызове таблицы DROP, пока таблица SELECT все еще активна. Вам действительно нужно заботиться о SQLITE_BUSY http://www.sqlite.org/cvstrac/wiki?p=DatabaseIsLocked – Lothar

9

Я понимаю, что это старая ветка, и ответы хорошие, но я изучал это недавно и натолкнулся на интересный анализ некоторых различных реализаций. В основном это касается сильных и слабых сторон соединения, обмена сообщениями, потоков-локальных подключений и пула соединений. Взгляните на это здесь: http://dev.yorhel.nl/doc/sqlaccess

1

Современные версии SQLite по умолчанию обеспечивают безопасность потоков.SQLITE_THREADSAFE флаг компиляции контролирует, включен или нет код в SQLite, чтобы обеспечить безопасную работу в многопоточной среде. По умолчанию значение - SQLITE_THREADSAFE=1. Это означает Serialized mode. В этом режиме:

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

Используйте функцию sqlite3_threadsafe() для проверки библиотеки Sqlite SQLITE_THREADSAFE флагом компиляции.

Поведение безопасности по умолчанию в библиотеке может быть изменено с помощью sqlite3_config(). Используйте SQLITE_OPEN_NOMUTEX и SQLITE_OPEN_FULLMUTEX флаги в sqlite3_open_v2(), чтобы настроить режим потоковой передачи отдельных подключений к базе данных.

0

Резюме

Операции в SQLite сериализуемы.

Изменения, внесенные в одно соединение с базой данных, невидимы для всех других соединений с базой данных до фиксации.

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

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

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

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


В дополнении к указанной выше информации о многопоточном доступе, может быть, стоит взглянуть на this page on isolation, так как многие вещи изменились с этим оригинальным вопросом и введения в журнале запись вперед (WAL).

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

Смежные вопросы