【sqllite3】Read error: SQLITE_BUSY: database is locked
文章目录
- 问题
- 概述
- 解决方案
- 解决 SQLITE_BUSY 数据库锁定问题
- 方案一:使用数据库连接池或单例连接
- 方案二:使用 WAL 模式(提高并发性能)
- 方案三:添加重试机制(处理临时锁定)
- 方案四:确保事务正确关闭
- 总结
问题
Uncaught Exception: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Read error: SQLITE_BUSY: database is locked".
概述
这个错误包含两个相关问题:SQLite 数据库锁定(SQLITE_BUSY) 和 未处理的 Promise 拒绝。以下是具体解决方案:
一、错误原因分析
SQLITE_BUSY: database is locked
SQLite 数据库同一时间只允许一个写操作,当多个进程 / 线程同时尝试读写时,会导致数据库锁定。常见原因:
多个数据库连接同时操作同一文件
事务未正确关闭,导致长期锁定
读写操作未加同步控制
未处理的 Promise 拒绝
数据库操作返回的 Promise 被拒绝(如锁定错误),但没有用 try/catch 或 .catch() 处理,导致应用崩溃。
解决方案
- 处理未处理的 Promise 拒绝(紧急修复)
首先确保所有数据库操作都有错误处理,避免应用崩溃:
// 错误示例:没有错误处理
async function readData() {const result = await db.get('SELECT * FROM table'); // 若失败会导致未处理的拒绝
}// 正确示例:添加 try/catch
async function readData() {try {const result = await db.get('SELECT * FROM table');return result;} catch (err) {console.error('数据库读取失败:', err); // 捕获并处理错误// 可选:重试逻辑或 fallbackif (err.message.includes('SQLITE_BUSY')) {console.log('数据库被锁定,稍后重试...');await new Promise(resolve => setTimeout(resolve, 100)); // 延迟后重试return readData(); // 重试}}
}
对所有数据库操作(get/all/run 等)添加类似的错误处理。
解决 SQLITE_BUSY 数据库锁定问题
方案一:使用数据库连接池或单例连接
确保整个应用使用单一数据库连接,避免多连接竞争:
// db.js - 单例模式管理数据库连接
const sqlite3 = require('sqlite3').verbose();
const dbPath = 'path/to/your/database.db';let dbInstance = null;function getDb() {if (!dbInstance) {dbInstance = new sqlite3.Database(dbPath, (err) => {if (err) {console.error('数据库连接失败:', err);}});}return dbInstance;
}module.exports = getDb;使用时始终通过 getDb() 获取连接,确保全局唯一:javascript
const getDb = require('./db');async function queryData() {const db = getDb();// 执行操作...
}
方案二:使用 WAL 模式(提高并发性能)
SQLite 的 WAL(Write-Ahead Logging)模式允许同时进行读写操作,减少锁定概率:
// 连接数据库时启用 WAL 模式
const db = new sqlite3.Database(dbPath, (err) => {if (err) {console.error('连接失败:', err);return;}// 启用 WAL 模式db.run('PRAGMA journal_mode=WAL;', (err) => {if (err) {console.error('启用 WAL 模式失败:', err);} else {console.log('WAL 模式已启用');}});
});
方案三:添加重试机制(处理临时锁定)
当检测到 SQLITE_BUSY 时,自动重试操作:
// 带重试的数据库操作工具函数
async function withRetry(operation, maxRetries = 5, delay = 100) {let retries = 0;while (retries < maxRetries) {try {return await operation();} catch (err) {retries++;if (err.message.includes('SQLITE_BUSY') && retries < maxRetries) {console.log(`数据库锁定,第 ${retries} 次重试...`);await new Promise(resolve => setTimeout(resolve, delay * retries)); // 指数退避} else {throw err; // 超过重试次数或其他错误,抛出异常}}}
}// 使用示例
async function readConfig() {return withRetry(async () => {const db = getDb();return new Promise((resolve, reject) => {db.get('SELECT * FROM config', (err, row) => {if (err) reject(err);else resolve(row);});});});
}
方案四:确保事务正确关闭
长时间未关闭的事务会导致数据库锁定,确保事务完成后及时提交或回滚:
async function updateData() {const db = getDb();try {await new Promise((resolve, reject) => {db.run('BEGIN TRANSACTION', (err) => {if (err) return reject(err);resolve();});});// 执行一系列操作await new Promise((resolve, reject) => {db.run('UPDATE table SET ...', (err) => {if (err) return reject(err);resolve();});});// 提交事务await new Promise((resolve, reject) => {db.run('COMMIT', (err) => {if (err) return reject(err);resolve();});});} catch (err) {// 出错时回滚await new Promise((resolve) => {db.run('ROLLBACK', resolve);});throw err;}
}
总结
优先处理未捕获的 Promise 拒绝:给所有异步数据库操作添加 try/catch,避免应用崩溃。
解决数据库锁定:
- 启用 WAL 模式(最简单有效)
- 使用单例连接
- 添加重试机制
- 确保事务及时关闭