我有一个服务器/客户端程序,其中服务器为每个连接的客户端生成一个带有fork((的子级。然后,客户端向服务器发送命令以在 SQLIT3 数据库上执行某些查询(插入、选择、删除(。
注意:我在 fork(( 之后打开数据库,如下所示:https://www.sqlite.org/howtocorrupt.html2.6
问题
当连接 1 个客户端时可以完美运行,但当连接了 2 个客户端时:当其中一个客户端执行修改数据库的第一个查询(例如:INSERT INTO(时,从那时起,只有它可以继续修改。其余的则出现错误:数据库已锁定。即使在阻止.db的客户端终止后,另一个客户端仍然无法修改。
我试过:
- PRAGMA journal_mode=walin sqlite3 terminal
- 手动执行BEGIN TRANSACTION;在 INSERT 查询和提交之前;在
- sqlite3_close(db(在插入查询之后,并在之后重新打开它
观察:
我知道使用线程可能会更安全并且可以解决问题,但是我想在更改整个项目结构之前尝试使用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);
}
}
}
谢谢你的时间!
答案
问题是数据库一次只能与一个进程通信,所以我在每次查询之前和之后打开和关闭数据库,现在它工作得很好。
现在仍然可能出现的唯一问题是如果多个进程同时运行查询,但这在我的程序中不会发生。
解决方案:这可以通过使用线程和互斥锁而不是 fork(( 来修复。
谢谢你的帮助
这是一个最小的测试程序,它分叉了一堆子进程,所有这些子进程都尝试一次插入一行,而无需将事务插入数据库。我无法复制您的结果 - 即使没有繁忙超时,多个进程也能够插入至少一些数据(尽管在这种情况下有很多锁定错误(。
这是否为您提供了与尝试时相同的行为?
#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: %sn", 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: %sn", 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: %sn", n,
sqlite3_errmsg(db));
} else {
fprintf(stderr, "child %d inserted %dn", 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;
}