Ошибка: база данных заблокирована при вызове sqlite3_exec() из дочернего элемента fork()

У меня есть серверная/клиентская программа, в которой сервер порождает дочерний элемент с fork() для каждого подключенного клиента. Затем клиент отправляет серверу команды для выполнения определенных запросов к базе данных sqlite3 (INSERT INTO, SELECT, DELETE FROM).
Примечание: я открываю базу данных после fork(), как указано здесь: https://www.sqlite.org/howtocorrupt.html в 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);
                }
         }
} 

Спасибо за ваше время!


person Stefan Moisanu    schedule 08.01.2020    source источник
comment
perror не подходит. Если sqlite_open3 возвращает что-то отличное от SQLITE_OK, правильное сообщение об ошибке можно получить от sqlite3_errmsg. Итак, какую ошибку вы на самом деле получаете?   -  person ikegami    schedule 08.01.2020
comment
Правда, я это исправлю, но БД открывается правильно, так как запросы могут выполняться нормально вне ситуации, которую я представил.   -  person Stefan Moisanu    schedule 08.01.2020
comment
Ах, вы говорите, что это exec не работает?   -  person ikegami    schedule 08.01.2020
comment
Вам действительно следует использовать подготовленный оператор и привязать значения, которые будут вставлены, к параметрам в это вместо создания строки с sprintf() и использованием sqlite3_exec(). Вот как вы получаете атаки SQL-инъекций, загадочные синтаксические ошибки и т. Д.   -  person Shawn    schedule 08.01.2020
comment
Да, printf(%s, sqlite3_errmsg(db)) печатает ошибку База данных заблокирована.   -  person Stefan Moisanu    schedule 08.01.2020
comment
Эта база данных случайно не находится в NFS или другой удаленной файловой системе?   -  person Shawn    schedule 08.01.2020
comment
Нет, это локально.   -  person Stefan Moisanu    schedule 08.01.2020
comment
А какую ОС вы используете?   -  person Shawn    schedule 08.01.2020
comment
@Shawn Я попробую это, но я считаю, что это не корень моей проблемы, поскольку запросы выполняются очень хорошо вне ситуации, которую я представил.   -  person Stefan Moisanu    schedule 08.01.2020
comment
@Шон Я использую Linux, Ubuntu   -  person Stefan Moisanu    schedule 08.01.2020
comment
Автономно, в виртуальной машине и т. д.?   -  person Shawn    schedule 08.01.2020
comment
@Shawn Это двойная загрузка с Windows, но я запускал свой код и на других ПК, та же проблема.   -  person Stefan Moisanu    schedule 08.01.2020


Ответы (2)


Отвечать

Проблема заключалась в том, что база данных могла взаимодействовать только с одним процессом за раз, поэтому я открывал и закрывал базу данных до и после каждого запроса, и теперь она работает отлично. запрос в то же время, но это не может произойти в моей программе.
Решение. Это можно исправить, используя потоки и мьютекс вместо fork().
Спасибо за помощь

person Stefan Moisanu    schedule 09.01.2020

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

Это дает вам то же поведение, которое вы видите, когда пытаетесь это сделать?

#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;
}
person Shawn    schedule 08.01.2020
comment
Да, я запустил ваш код, вот результат: (я убрал некоторые строки, чтобы поместиться в комментарий, но они составляют около 50-50% ошибок и успешных вставок) sqlite3_step в дочернем 1: база данных заблокирована sqlite3_step в дочернем 2: база данных заблокирован sqlite3_step в дочернем 3: база данных заблокирована sqlite3_step в дочернем 4: база данных заблокирована дочерний 0 вставлен 0 дочерний 2 вставлен 1 sqlite3_step в дочерний 3: база данных заблокирована sqlite3_step в дочернем 4: база данных заблокирована sqlite3_step в дочернем 0: база данных заблокирована 1 вставлен 1 sqlite3_step в дочерний элемент 2: база данных заблокирована дочерний элемент 3 вставлен 2 - person Stefan Moisanu; 08.01.2020