sqlite3_step (stmt) внутри транзакции завершается с ошибкой 5 без вызова моего обработчика занятости - PullRequest
1 голос
/ 14 июля 2020

[ sqlite версия 3.28.0 (2019-04-16)]

Я использую sqlite в многопоточном application.

Потоковый режим Sqlite настроен для многопоточности (флаг компиляции: SQLITE_THREADSAFE=2).

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

В общем, все работает хорошо, однако я обнаружил, что в следующем сценарии мой обработчик занятости не вызывается:

  1. Код начала транзакции (BEGIN TRANSACTION),
  2. Код, работающий с sqlite-оператором (sqlite3_prepare_v2(), sqlite3_bind_xxx()),
  3. В конце концов при звонке sqlite3_step() я получаю 'база данных заблокирована' (ошибка 5) без вызова моего обработчика занятости .

Я знаю, что в документации sqlite говорится:

Наличие обработчика занятости не гарантирует, что он будет вызван при конфликте блокировок. Если SQLite определяет, что вызов обработчика занятости может привести к тупиковой ситуации, он будет go впереди и вернет SQLITE_BUSY

Однако мое условие - простое автономное приложение, которое не использовать базу данных совместно с другими приложениями. По этой причине я не могу понять, почему мой обратный вызов обработчика занятости не вызывается.

Мой вопрос:

Есть ли способ настроить sqlite , чтобы всегда вызывать мой обратный вызов обработчика занятости?

1 Ответ

0 голосов
/ 17 июля 2020

... после консультации с форумом SQLite ...

Решение:

Замените команду BEGIN TRANSACTION на BEGIN IMMEDIATE TRANSACTION.

Подробнее

В sqlite есть три типа транзакций:

  1. Deferred ( значение по умолчанию)
  2. Немедленно,
  3. Эксклюзивное

Если тип транзакции не указан явно в операторе BEGIN TRANSACTION, то значение по умолчанию DEFERRED выбран тип-транзакции.

В DEFERRED тип-транзакции sqlite не будет пытаться получить какие-либо блокировки, пока не встретит первую команду, требующую этой блокировки.

Итак, если транзакция содержала одну команду READ и одну команду WRITE , тогда sqlite будет делать:

BEGIN TRANSACTION  //  No locks acquired
    SELECT...      //  Try to acquire READ  lock
    INSERT...      //  Try to acquire WRITE lock

В мульти- потоковой среды, если вышеуказанная транзакция будет выполняться одновременно двумя (или более) разными потоками, может возникнуть взаимоблокировка:

  1. Thread-A a cquires READ lock,
  2. Thread-B получает READ lock,
  3. Thread-A пытается получить WRITE lock , который может быть получен только после того, как все блокировки READ и WRITE будут сняты,
  4. Thread-B пытается получить блокировку WRITE .. .

Это состояние является взаимоблокировкой , и, как объясняется в документации sqlite : если обнаружена потенциальная взаимоблокировка, то обработчик занятости не будет вызван как он не может разрешить это условие.

Выше описано, как работает DEFERRED транзакция.

В обоих случаях - IMMEDIATE и EXCLUSIVE - блокировка WRITE приобретается прямо в начале транзакции , поэтому нет риска возникновения взаимоблокировок:

BEGIN IMMEDIATE TRANSACTION  //  Try to acquire the WRITE lock
    SELECT...                //  ...no locks activity...
    INSERT...                //  ...no locks activity...

Поскольку мой код использует sqlite в многопоточном режим многопоточности Мне пришлось заменить мою команду BEGIN TRANSACTION на BEGIN IMMEDIATE TRANSACTION.

Это решило проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...