Как использовать SQLite в многопоточном приложении?
Я разрабатываю приложение с SQLite в качестве базы данных, и у меня есть небольшая проблема с пониманием того, как использовать его в несколько потоков (ни один из других вопросов действительно не помог мне, к сожалению).
Мой прецедент: в базе данных есть одна таблица, позвольте ей "A", которая имеет разные группы строк (на основе одного из своих столбцов). У меня есть "основной поток" приложения, который читает содержимое из таблицы A. Кроме того, я иногда решает обновить определенную группу строк. Для этого я хочу создать новый поток, удалить все строки группы и повторно вставить их (это единственный способ сделать это в контексте моего приложения). Это может произойти с разными группами одновременно, поэтому у меня могут быть 2+ темы, пытающиеся обновить базу данных.
Я использую разные транзакции из каждого потока, I.E. в начале каждого цикла обновления потока у меня есть начало. Фактически, каждый поток фактически выполняет вызов "BEGIN", удаляет из базы данных все строки, необходимые для "обновления", и вставляет их снова с новыми значениями (это так, как это должно быть сделано в контексте моего приложение).
Теперь я пытаюсь понять, как я это делаю. Я пробовал читать (другие ответы на Stack Overflow, сайт SQLite), но я не нашел ответы на все вопросы. Вот некоторые вещи, о которых мне интересно:
- Нужно ли мне "открывать" и создавать новую структуру sqlite из каждого потока?
- Нужно ли мне добавлять какой-либо специальный код для всего этого или достаточно, чтобы порождать разные потоки, обновлять строки, и это прекрасно (поскольку я использую разные транзакции)?
- Я видел что-то, говоря о различных типах блокировок, и о том, что я мог бы получить "SQLite занят" от вызова определенных API, но, честно говоря, я не видел ссылок, которые полностью объяснялись, когда мне нужно было все это в учетную запись. Нужно ли мне?
Если кто-то может ответить на вопросы/указать мне в сторону хорошего ресурса, я был бы очень благодарен.
ОБНОВЛЕНИЕ 1: Из всего, что я читал до сих пор, похоже, что у вас не может быть двух потоков, которые все равно будут записывать в файл базы данных.
Смотрите: http://www.sqlite.org/lockingv3.html. В разделе 3.0: ЗАБРОНИРОВАННАЯ блокировка означает, что процесс планирует записать в файл базы данных в какой-то момент в будущем, но в настоящее время он просто считывает из файла. За один раз может быть активен только один замок RESERVED, хотя несколько замков SHARED могут сосуществовать с одной блокировкой RESERVED.
Означает ли это, что я могу только порождать один поток, чтобы обновлять группу строк каждый раз? У меня есть какой-то поток poller, который решает, что мне нужно обновить некоторые из строк, а затем создает новый поток для этого, но не более одного за раз? Так как это похоже на то, что любой другой поток, который я создаю, просто получит SQLITE_BUSY до тех пор, пока не закончится первый поток.
Я правильно понял вещи?
Кстати, спасибо за ответы до сих пор, они очень помогли.
Ответы
Ответ 1
Отметьте эту ссылку. Самый простой способ - сделать блокировку самостоятельно и не допускать совместного использования потоков между потоками. Еще один хороший ресурс можно найти здесь, и он заканчивается:
-
Убедитесь, что вы компилируете SQLite с -DTHREADSAFE = 1.
-
Убедитесь, что каждый поток открывает файл базы данных и сохраняет свою собственную структуру sqlite.
-
Убедитесь, что вы обрабатываете вероятную возможность столкновения одного или нескольких потоков при одновременном доступе к файлу db: соответствующим образом обрабатывайте SQLITE_BUSY.
-
Убедитесь, что вы заключили в транзакции команды, которые изменяют файл базы данных, например INSERT, UPDATE, DELETE и другие.
Ответ 2
Некоторые шаги при запуске с SQLlite для многопоточного использования:
- Убедитесь, что sqlite скомпилирован с помощью многопоточного флага.
- Вы должны открыть open в своем файле sqlite, чтобы создать соединение в каждом потоке, не разделять соединения между потоками.
- SQLite имеет очень консервативную модель потоков, когда вы выполняете операцию записи, которая включает в себя открытие транзакций, которые собираются выполнить INSERT/UPDATE/DELETE, другие потоки будут заблокированы до завершения этой операции.
- Если вы не используете транзакцию, транзакции неявны, поэтому, если вы запустите INSERT/DELETE/UPDATE, sqlite попытается получить эксклюзивную блокировку и завершить операцию до ее выпуска.
- Если вы выполняете оператор BEGIN EXCLUSIVE, он будет приобретать эксклюзивную блокировку перед выполнением операций в этой транзакции. COMMIT или ROLLBACK освободят блокировку.
- Ваши sqlite3_step, sqlite3_prepare и некоторые другие вызовы могут возвращать SQLITE_BUSY или SQLITE_LOCKED. SQLITE_BUSY обычно означает, что sqlite необходимо получить блокировку. Самая большая разница между двумя возвращаемыми значениями:
- SQLITE_LOCKED: если вы получаете это из инструкции sqlite3_step, вы ДОЛЖНЫ вызывать sqlite3_reset в дескрипторе оператора. Вы должны получить это только при первом вызове sqlite3_step, поэтому после вызова reset вы можете "повторить" ваш вызов sqlite3_step. В других операциях он аналогичен SQLITE_BUSY
- SQLITE_BUSY: нет необходимости вызывать sqlite3_reset, просто повторите операцию после ожидания бит для блокировки, которая будет выпущена.
Ответ 3
Я понимаю, что это старый поток, и ответы хорошие, но я изучал это недавно и наткнулся на интересный анализ некоторых различных реализаций. В основном это касается сильных и слабых сторон соединения, обмена сообщениями, потоков-локальных подключений и пула соединений. Взгляните на это здесь: http://dev.yorhel.nl/doc/sqlaccess
Ответ 4
Проверьте этот код из вики-страницы SQLite.
Я сделал что-то подобное с C, и я загрузил код здесь.
Надеюсь, это полезно.
Ответ 5
В современных версиях SQLite по умолчанию включена защита потоков. SQLITE_THREADSAFE
флаг компиляции определяет, включен или нет код в SQLite, чтобы он мог безопасно работать в многопоточной среде. Значение по умолчанию SQLITE_THREADSAFE=1
. Это означает Сериализованный режим. В этом режиме:
В этом режиме (который по умолчанию используется при компиляции SQLite с SQLITE_THREADSAFE = 1), библиотека SQLite сама будет сериализовать доступ к соединениям с базой данных и подготовленным операциям, чтобы приложение могло использовать одно и то же соединение с базой данных или тот же подготовленный оператор в разных потоках одновременно.
Используйте функцию sqlite3_threadsafe()
, чтобы проверить флаг компиляции Sqlite library SQLITE_THREADSAFE
.
Поведение безопасности нити библиотеки по умолчанию можно изменить с помощью sqlite3_config()
. Используйте флажки SQLITE_OPEN_NOMUTEX
и SQLITE_OPEN_FULLMUTEX
в sqlite3_open_v2()
, чтобы настроить режим потоковой передачи отдельных подключений к базе данных.
Ответ 6
Резюме
Транзакции в SQLite являются SERIALIZABLE.
Изменения, сделанные в одном соединении с базой данных, невидимы для всех других соединений с базой данных до фиксации.
В запросе отображаются все изменения, которые были завершены в одном соединении с базой данных до начала запроса, независимо от того, были ли эти изменения зафиксированы.
Если изменения происходят в одном соединении с базой данных после запуска запроса, но до завершения запроса, то это undefined, будет ли запрос видеть эти изменения.
Если изменения происходят в одном соединении с базой данных после запуска запроса, но до завершения запроса запрос может возвращать измененную строку более одного раза или может вернуть ранее удаленную строку.
Для целей предыдущих четырех элементов два соединения с базой данных, которые используют один и тот же общий кэш и которые включают PRAGMA read_uncommitted
, считаются одним и тем же соединением с базой данных, а не отдельными соединениями с базой данных.
В дополнение к приведенной выше информации о многопоточном доступе, возможно, стоит взглянуть на эту страницу в изоляции, так как многие вещи изменились после этого первоначального вопроса и введения журнала записи вперед (WAL).
Кажется, гибридный подход, заключающийся в том, что несколько подключений, открытых для базы данных, обеспечивают адекватные гарантии concurrency, торгующие за счет открытия нового соединения с учетом возможности многопоточных транзакций записи.