【问题标题】:sqlite3 db opened in read-only fires SQLITE_BUSYsqlite3 db 以只读方式打开会触发 SQLITE_BUSY
【发布时间】:2020-04-08 15:26:45
【问题描述】:

我在以只读方式访问 sqlite3 数据库时遇到了一些问题。 在系统(Linux 嵌入式)上,有一个 C++ 应用程序打开(并保持打开)到数据库的 r/w 连接。此应用程序几乎连续地进行读写。

然后有一个 PHP Web 应用程序,它每 1 秒打开一个只读连接以获取数据。 好像一点问题都没有。

最后有一个 C 应用程序应该打开另一个到同一个数据库的只读连接并每隔几秒获取数据。此应用程序经常遇到busy error

据我从帮助和有关此主题的其他问题中了解到,这通常在您尝试编写并且有另一个正在运行的写入事务时触发。 帮助还说“或在某些情况下阅读”,但我无法弄清楚这些情况。

可能会在 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;

代码或整个方法是否有明显错误?

【问题讨论】:

  • 您应该检查sqlite3_open_v2() 的返回值是否成功。即使它失败了,它也会分配一个必须用sqlite3_close() 释放的sqlite3 结构,因此测试该指针以查看它是否为空是行不通的。
  • 还可以查看sqlite3_busy_timeout() 函数。如果你还没有使用 WAL 模式。
  • @Shawn,关于sqlite3_busy_timeout() 我不确定它是否仅适用于写入。

标签: c sqlite


【解决方案1】:

大概prepare需要一个独占权限来读取db的当前schema数据。

一种可能的解决方案是使用 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);

最大重试次数和休眠时间(以毫秒为单位)应根据您的要求确定。

就像在 cmets 中已经提到的那样,检查 sqlite3_open_v2 调用的返回码是有意义的。

大概也可以像这样使用准备好的语句:

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

并将参数绑定:

sqlite3_bind_int(stmt, 1, addr);

如何测试

对于测试,可以使用可以在这个很好的答案中找到的技巧:https://stackoverflow.com/a/57786662/2331445

例如,可以简单地将睡眠时间临时设置为 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 键。现在它将显示来自busy_handler 的printfs。一旦我们用一个

commit;

返回结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-10
    • 2014-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-10
    相关资源
    最近更新 更多