sqlite3 db открыт в режимах только для чтения SQLITE_BUSY - PullRequest
0 голосов
/ 08 апреля 2020

У меня есть некоторые проблемы при доступе к базе данных sqlite3 только для чтения. В системе (Linux встроенный) есть приложение C ++, которое открывает (и сохраняет открытым) соединение ar / w с БД. Это приложение читает и пишет почти непрерывно.

Затем существует веб-приложение PHP, которое открывает подключения только для чтения каждые 1 сек c для извлечения данных. Кажется, у него вообще нет проблем.

Наконец, есть приложение C, которое должно открывать другое соединение только для чтения с той же БД и извлекать данные каждые несколько секунд. Это приложение часто сталкивается с занятой ошибкой .

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

Может случиться попытка чтения, когда приложение C ++ все еще пишет, но, насколько я знаю, это не должно привести на эту ошибку. Запрос на чтение вернет «старые» значения, но должен завершиться без проблем.

Это правильно?

Здесь код C, который возвращает эту ошибку:

sqlite3 *db;
sqlite3_stmt *stmt;
char *qry=0;
int rc,value=0;

sqlite3_open_v2( "<mydb>", &db,SQLITE_OPEN_READONLY,NULL);
if (db == NULL)
{
    syslog(LOG_INFO,"Failed to open db\n");
    return -1;
}

qry=malloc(80);

if (qry) {
    sprintf (qry,"select value from dataplc WHERE address=%d",addr);

    rc = sqlite3_prepare_v2(db, qry,-1,&stmt,NULL);
    if (rc != SQLITE_OK) {
        syslog(LOG_INFO, "Failed to read: %d - rc: %d", addr,rc); // -5 -> db busy
        value = -1;
    } else {
        rc = sqlite3_step(stmt);
        if (rc == SQLITE_ROW) {
           value = sqlite3_column_int(stmt, 0);
           // do something
        }
    }
    free (qry);
}
sqlite3_finalize(stmt);   
sqlite3_close(db);
return value;

Что-то явно не так в коде или во всем подходе?

1 Ответ

1 голос
/ 09 апреля 2020

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

Одним из возможных решений будет использование sqlite3_busy_handler, например,

#define ABORT 0
#define CONTINUE 1

int busy_handler(void *data, int attempt) {
    printf("attempt: %d\n", attempt);
    if(attempt < 10) {
        sqlite3_sleep(250);
        return CONTINUE;
    }
    return ABORT;
}

и установка его перед вызов sqlite3_prepare_v2 примерно так:

sqlite3_busy_handler(db, busy_handler, NULL);

Максимальное количество попыток и время ожидания (в миллисекундах) должны определяться на основе ваших требований.

Как уже упоминалось в Комментарии имеет смысл проверить код возврата вызова sqlite3_open_v2.

Предположительно, было бы также целесообразно использовать подготовленное утверждение следующим образом:

rc = sqlite3_prepare_v2(db, "SELECT value FROM dataplc WHERE address = ?1", -1, &stmt, NULL);

и связать параметр с помощью:

sqlite3_bind_int(stmt, 1, addr);

Как проверить

Для тестирования можно использовать трюк, который можно найти в этом прекрасном ответе: { ссылка }:

Можно просто установить время сна, например временно до 1 секунды (в busy_handler sqlite3_sleep(1000);).

Затем добавьте getchar(); непосредственно перед подготовкой.

Ваша программа, слегка измененная в отношении вышеупомянутых пунктов, будет выглядеть так:

#include <stdio.h>
#include <sys/syslog.h>
#include "sqlite3.h"

#define ABORT 0
#define CONTINUE 1

int busy_handler(void *data, int attempt) {
    printf("attempt: %d\n", attempt);
    if (attempt < 10) {
        sqlite3_sleep(1000);
        return CONTINUE;
    }
    return ABORT;
}

int read_from_db(int addr) {
    sqlite3 *db;
    sqlite3_stmt *stmt;
    int rc, value = 0;

    rc = sqlite3_open_v2("mydb", &db, SQLITE_OPEN_READONLY, NULL);
    if (rc != SQLITE_OK) {
        sqlite3_close(db);
        syslog(LOG_INFO, "Failed to open db\n");
        return -1;
    }

    sqlite3_busy_handler(db, busy_handler, NULL);

    printf("press enter to continue:\n");
    getchar(); //only for testing

    rc = sqlite3_prepare_v2(db, "SELECT value FROM dataplc WHERE address = ?1", -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        syslog(LOG_INFO, "Failed to read: %d - rc: %d", addr, rc); // -5 -> db busy
        value = -1;
    } else {
        sqlite3_bind_int(stmt, 1, addr);

        rc = sqlite3_step(stmt);
        if (rc == SQLITE_ROW) {
            value = sqlite3_column_int(stmt, 0);
            // do something
        }
    }

    sqlite3_finalize(stmt);
    sqlite3_close(db);
    return value;
}


int main(void) {
    int val = read_from_db(42);
    printf("result: %d\n", val);
    return 0;
}

В командной строке sqlite3 В интерфейс можно ввести:

begin exclusive;

Затем на консоли, где запускается программа, вы можете нажать клавишу ENTER. Теперь он покажет printfs из busy_handler. Как только мы освободим базу данных с

commit;

, результат будет возвращен.

screencast

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