【问题标题】:Error: Database is locked when calling sqlite3_exec() from fork() child错误:从 fork() 子调用 sqlite3_exec() 时数据库被锁定
【发布时间】:2020-01-10 03:52:12
【问题描述】:

我有一个服务器/客户端程序,服务器为每个连接的客户端生成一个带有 fork() 的子程序。然后,客户端向服务器发送命令以在 sqlite3 数据库上执行某些查询(INSERT INTO、SELECT、DELETE FROM)。
注意:我在 fork() 之后打开数据库,如下所示:@ 987654321@ 2.6

问题

当 1 个客户端连接时完美运行,但当有 2 个客户端连接时:当其中一个执行修改数据库的第一个查询(例如:INSERT INTO)时,从那时起只有它可以继续修改。其余的得到错误:数据库已锁定。即使在阻塞.db的客户端终止后,对方仍然无法修改。

我试过了:

  • PRAGMA journal_mode=wal 在 sqlite3 终端中
  • 手动执行BEGIN TRANSACTION;在INSERT查询之前和COMMIT;之后
  • sqlite3_close(db) 在 INSERT 查询之后并在之后重新打开它

观察:

我知道使用线程可能会更安全并且可以解决问题,但我想在更改整个项目结构之前尝试使用 fork() 进行所有操作。

服务器代码(我删除了不相关的代码部分):

int main () {
   sqlite3* db;
   //code for connection...
   while (1) {
        client = accept(sd, (struct sockaddr *) &from, &length);
        if (-1 == (pid = fork())) {
            perror("Error at fork");
        }
        if (pid == 0) {
            if (sqlite3_open("./Database.db", &db))
            perror("Error: Could not open database.\n")
            while (1) {
                sprintf(query, "INSERT INTO table VALUES ('%s','%s','%s','%s','%s','%s');",string1, string2,string3,string4,string5,string6);
                if(sqlite3_exec(db, query, NULL, 0, &error) != SQLITE_OK){
                      printf("%s", sqlite3_errmsg(db));
                      fflush(stdout);
                }
         }
} 

感谢您的宝贵时间!

【问题讨论】:

  • perror 不合适。如果sqlite_open3 返回的不是SQLITE_OK,则可以从sqlite3_errmsg 获得正确的错误信息。那么,您实际上遇到了什么错误?
  • 没错,我会解决这个问题,但数据库会正确打开,因为查询可以在我提出的情况之外正常执行
  • 啊,你是说exec失败了?
  • 您确实应该使用prepared statement 并将要插入的值绑定到其中的参数,而不是使用sprintf() 构建字符串并使用sqlite3_exec()。这就是你如何获得 sql 注入攻击、神秘的语法错误等。
  • 是的,printf("%s", sqlite3_errmsg(db)) 打印错误数据库已锁定。

标签: c database sqlite fork blocked


【解决方案1】:

回答

问题是数据库一次只能与一个进程通信,所以我在每次查询之前和之后打开和关闭数据库,现在它可以正常工作了。
现在仍然会出现的唯一问题是如果多个进程同时运行查询,但这在我的程序中不会发生。
解决方案: 这可以通过使用线程和互斥体而不是 fork() 来解决。
感谢您的帮助

【讨论】:

    【解决方案2】:

    这是一个最小的测试程序,它派生出一堆子进程,所有这些子进程都试图一次将单行插入到数据库中,而没有事务。我无法复制您的结果 - 即使没有繁忙的超时,也有多个进程能够插入至少一些数据(尽管在这种情况下会出现很多锁定错误)。

    这是否与您在尝试时看到的行为相同?

    #include <sqlite3.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    #define TRIALS 5
    
    // The smaller the timeout, the more likely to get a locked error.
    #define TIMEOUT 200
    
    int main(void) {
      for (int n = 0; n < TRIALS; n++) {
        pid_t pid = fork();
        if (pid < 0) {
          perror("fork");
          return EXIT_FAILURE;
        }
        if (pid == 0) {
          sqlite3 *db;
          sqlite3_stmt *stmt;
          // test.db needs a table named test like:
          // CREATE TABLE test(a,b)
          if (sqlite3_open("test.db", &db) != SQLITE_OK) {
            fprintf(stderr, "sqlite3_open in child %d: %s\n", n,
                    sqlite3_errmsg(db));
            sqlite3_close(db);
            return EXIT_FAILURE;
          }
          sqlite3_busy_timeout(db, TIMEOUT);
          if (sqlite3_prepare_v2(db, "INSERT INTO test VALUES (?, ?)", -1, &stmt,
                                 NULL) != SQLITE_OK) {
            fprintf(stderr, "sqlite3_prepare_v2 in child %d: %s\n", n,
                    sqlite3_errmsg(db));
            sqlite3_close(db);
            return EXIT_FAILURE;
          }
          sqlite3_bind_int(stmt, 1, n);
          for (int m = 0; m < TRIALS; m++) {
            sqlite3_bind_int(stmt, 2, m);
            if (sqlite3_step(stmt) != SQLITE_DONE) {
              fprintf(stderr, "sqlite3_step in child %d: %s\n", n,
                      sqlite3_errmsg(db));
            } else {
              fprintf(stderr, "child %d inserted %d\n", n, m);
            }
            sqlite3_reset(stmt);
            // Sleep for a short time to allow another process to run.
            // Otherwise this loop runs fast enough that it might as well
            // be done in serial, not parallel.
            unsigned int p;
            sqlite3_randomness(sizeof p, &p);
            sqlite3_sleep((p % 50) + 1);
          }
          sqlite3_finalize(stmt);
          sqlite3_close(db);
          return 0;
        }
      }
      for (int n = 0; n < TRIALS; n++) {
        wait(NULL);
      }
      return 0;
    }
    

    【讨论】:

    • 是的,我运行了您的代码,这是输出:(我删除了一些行以适应注释,但它们大约有 50-50% 的错误和成功插入)孩子 1 中的 sqlite3_step:数据库是在子节点 2 中锁定 sqlite3_step:在子节点 3 中锁定数据库 sqlite3_step:在子节点 4 中锁定数据库 sqlite3_step:在子节点 4 中锁定数据库:子节点 0 插入 0 子节点 2 在子节点 3 中插入​​ 1 sqlite3_step:数据库在子节点 4 中锁定 sqlite3_step:数据库被锁定 sqlite3_step在子 0:数据库被锁定子 1 插入 1 sqlite3_step 在子 2:数据库被锁定子 3 插入 2
    猜你喜欢
    • 2012-02-09
    • 2018-06-11
    • 1970-01-01
    • 1970-01-01
    • 2013-04-18
    • 1970-01-01
    • 1970-01-01
    • 2015-12-05
    • 1970-01-01
    相关资源
    最近更新 更多