解决SQLITE database.locked错误:单线程下事务串行执行方案
解决SQLite事务冲突导致的database.locked问题
你的问题核心在于信号打断了正在执行的数据库事务:当你的单线程正在dbExecSql的事务(从BEGIN到END)过程中,其他线程发来的信号会直接中断当前执行流程,转而调用dbDelete。而SQLite的单个连接是单线程独占的,不允许在一个未完成的事务上执行其他操作(尤其是DETACH这种对连接状态有影响的命令),这就直接触发了database.locked错误。
下面是具体的解决思路和代码修改方案:
核心思路:让维护操作(dbDelete)排队等待事务完成
不要在信号处理函数里直接执行dbDelete,而是用异步安全的标志位标记需要执行维护操作,然后在dbExecSql的事务安全节点(每次事务结束后)检查并执行dbDelete,确保两个操作不会重叠。
步骤1:添加维护状态标记
在你的Database类中添加两个原子变量(保证线程安全和异步信号安全):
#include <atomic> class Database { private: // 标记是否有等待执行的维护任务 std::atomic<bool> m_pending_maintenance{false}; // 标记当前是否正在执行维护操作,防止重复触发 std::atomic<bool> m_in_maintenance{false}; // 其他成员变量... };
步骤2:修改信号触发逻辑
其他线程不要直接调用dbDelete,而是设置m_pending_maintenance为true。如果是用POSIX信号,信号处理函数里只能做异步安全的操作,所以只设置原子变量:
// 信号处理函数(异步安全) void handle_maintenance_signal(int signum) { // 假设db是全局或者可以安全访问的Database实例指针 db->m_pending_maintenance = true; }
步骤3:修改dbExecSql,在安全点检查维护任务
在dbExecSql的事务开始前和结束后,检查是否有等待执行的维护任务,确保只有当前事务完成后才执行dbDelete:
int Database::dbExecSql() { // 事务开始前先检查维护任务(避免刚启动就被打断) while (m_pending_maintenance && !m_in_maintenance) { m_in_maintenance = true; dbDelete(); m_in_maintenance = false; m_pending_maintenance = false; } // 执行正常的写入事务 sqlite3_stmt* stmt = nullptr; int rc = SQLITE_OK; rc = sqlite3_prepare_v2(db, insertTableSQL.c_str(), -1, &stmt, 0); if (rc != SQLITE_OK) { // 错误处理... return rc; } rc = sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL); if (rc != SQLITE_OK) { sqlite3_finalize(stmt); // 错误处理... return rc; } // 绑定、执行插入逻辑... rc = sqlite3_bind_int(stmt, j + 1, insertValueInt); // ...省略其他绑定和执行代码 rc = sqlite3_step(stmt); sqlite3_clear_bindings(stmt); sqlite3_reset(stmt); // 提交事务 rc = sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL); if (rc != SQLITE_OK) { sqlite3_finalize(stmt); // 错误处理... return rc; } rc = sqlite3_finalize(stmt); if (rc != SQLITE_OK) { // 错误处理... return rc; } // 事务结束后再次检查维护任务 if (m_pending_maintenance && !m_in_maintenance) { m_in_maintenance = true; dbDelete(); m_in_maintenance = false; m_pending_maintenance = false; } return rc; }
步骤4:优化dbDelete的原子性
把dbDelete的所有操作包裹在一个事务里(虽然ATTACH/DETACH在事务中的行为特殊,但可以确保整个维护操作是连续执行的,不会被其他逻辑打断):
int Database::dbDelete() { int rc = SQLITE_OK; char* err_msg = nullptr; // 开始维护事务 rc = sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &err_msg); if (rc != SQLITE_OK) { cout << "ERROR: Maintenance Transaction Start: " << err_msg << endl; sqlite3_free(err_msg); return rc; } // 原有ATTACH、COPY、DETACH、DELETE逻辑... attachQuery = "ATTACH DATABASE '" + db_log_dir + "/" + system_id + "_" + dbLogName + ".sql'" + " AS '" + system_id + "_" + dbLogName + "';"; detachQuery = "DETACH DATABASE '" + system_id + "_" + dbLogName + "';"; copyTableQuery = "CREATE TABLE '" + system_id + "_" + dbLogName + "'.'" + tableName + "_" + dbLogName + "' AS SELECT * FROM main." + tableName; deleteQuery = dbDeleteSelected(tableName, timeStart, customLog); rc = sqlite3_exec(db, attachQuery.c_str(), NULL, 0, &err_msg); cout << "ATTACH: " << rc << endl; if (rc != SQLITE_OK) { cout << "ERROR: Hour Log Attach: " << err_msg << endl; cout << "SQL -> " << attachQuery.c_str() << endl; sqlite3_free(err_msg); sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL); return rc; } // ...省略COPY、DETACH、DELETE的原有逻辑 // 提交维护事务 rc = sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &err_msg); if (rc != SQLITE_OK) { cout << "ERROR: Maintenance Transaction End: " << err_msg << endl; sqlite3_free(err_msg); return rc; } return rc; }
为什么这样能解决问题?
- 我们把
dbDelete的执行时机从信号中断点,转移到了dbExecSql事务的安全间隙(事务完成后),确保同一时刻只有一个数据库操作在执行。 - 原子变量保证了线程间和信号处理中的状态修改是安全的,不会出现竞态条件。
- 给
dbDelete加上事务,确保整个维护操作是原子性的,不会被中途打断。
内容的提问来源于stack exchange,提问作者user6868820
相关产品推荐
相关产品推荐

