SQLite并发访问出现The database file is locked , database is locked

本文介绍了SQLite数据库的特点,包括其轻量级、支持事务处理及并发性的特性。针对多人并发使用SQLite时可能出现的“数据库锁定”问题,文章提出了通过定义全局锁变量的方法来提升并发度。

在站点服务端使用SQlite存储一些临时数据,但是在多人并发的时候Sqlite会抛出异常:The database file is locked , database is locked

Sqlite的特点:

  1. 简单(simple):SQLite是一个非常轻量级自包含(lightweight and self-contained)的DBMS:一个头文件,一个动态库文件,你就拥有了关系数据库的所有功能了。简单,是SQLite最明显的哲学。它提供的API少而简单。只需要一个DLL文件,你的程序马上就拥有了一个功能强大的数据库引擎,这是一件很美妙的事。
  2. 小巧(small):我用VS 2005在Windows下编译的3.6.11,Release版为368K,用时不到20秒——而编译MySQL时,要花上几分钟。而当我插入10000条int数据时,内存开销660K,磁盘开销92K。
  3. 事务(transaction):事务是现代商业数据处理系统最基本的要求,而Access,不论是在可执行文件大小(看了一下Access2003的可执行文件大小为6.32M,两者不是一个量级),还是事务特性,都是不能和SQLite 相比的。
  4. 并发性(Concurrency):由于SQLite通过OS的文件锁来实现库级锁,粒度很大,但是,它通过一些复杂特殊的处理(具体可以参见分析系列),尽量的提升了读写的并发度。
  5. SQL92:SQLite支持绝大部分的标准SQL语句,你只需要几百K的空间,就可以换来需要上百兆的通用DBMS几乎所有操作了。
  6. 方便(Convenience):如果你的程序要使用SQLite,只需要将拷贝你的程序目录即可。
  7. 开源(Opensource):这是它最强大的地方。开源,意味着你可以品读它的源码,你可以随时修改它,加入你自己的特性,而这一切完全免费的。

  SQLite只支持库级锁,库级锁意味着什么?——意味着同时只能允许一个写操作,也就是说,即事务T1在A表插入一条数据,事务T2在B表中插入一条数据,这两个操作不能同时进行,即使你的机器有100个CPU,也无法同时进行,而只能顺序进行。表级都不能并行,更别说元组级了——这就是库级锁。但是,SQLite尽量延迟申请X锁,直到数据块真正写盘时才申请X锁,这是非常巧妙而有效的。

Sqlite不支持并发执行写入操作,即使是不同的表,只支持库级锁,而且这个Sqlite本身没有实现,必须自己实现这个库级锁。

综上所述,对于库级锁的数据库,可以定义一个全局的锁变量,在所有需要发起事务的操作中使用同步机制访问数据库。

class PublicationEventHandler implements JobEventHandler {
    public static final Object SQLITE_DBLEVEL_LOCK = new Object();
    @Override
    public void onCompleted(JobEvent event) {
	synchronized (SQLITE_DBLEVEL_LOCK) {
	    ServiceLocator.getJobNotificationService().markJobNotified(event.getJobCommand().getIdentity(),
		    JobStatusDefinition.COMPLETE);
	}

    }

    @Override
    public void onCancelled(JobEvent event) {
	synchronized (SQLITE_DBLEVEL_LOCK) {
	    ServiceLocator.getJobNotificationService().markJobNotified(event.getJobCommand().getIdentity(),
		    JobStatusDefinition.CANCEL);
	}
    }

    @Override
    public void onFailed(JobEvent event) {
	synchronized (SQLITE_DBLEVEL_LOCK) {
	    ServiceLocator.getJobNotificationService().markJobNotified(event.getJobCommand().getIdentity(),
		    JobStatusDefinition.FAIL);
	}
    }

    @Override
    public void onStart(JobEvent event) {
	synchronized (SQLITE_DBLEVEL_LOCK) {
	    ServiceLocator.getJobNotificationService().addJobNotification(event.getJobCommand().getIdentity());
	}
    }

}


### 解决 SQLite `SQLITE_BUSY` 错误的方法 当遇到 `SQLITE_BUSY` 错误时,通常是因为数据库文件被其他连接占用或锁定。以下是详细的解决方案: #### 1. 使用 WAL 模式减少锁冲突 SQLite 提供了一种称为 Write-Ahead Logging (WAL) 的模式,在这种模式下,多个读者可以在一个写者工作的同时继续访问数据库[^1]。启用 WAL 模式的代码如下: ```python import sqlite3 conn = sqlite3.connect('example.db') cursor = conn.cursor() cursor.execute("PRAGMA journal_mode=WAL;") conn.commit() ``` 通过切换到 WAL 模式,可以显著降低因锁而导致的 `SQLITE_BUSY` 错误。 --- #### 2. 增加超时时间设置 可以通过调整 SQLite 的超时参数来延长等待锁释放的时间。默认情况下,SQLite 只会短暂地尝试获取锁,如果失败则立即返回 `SQLITE_BUSY` 错误。增加超时时间可以让程序有更多机会成功执行查询。 ```c sqlite3* db; int rc = sqlite3_open_v2("example.db", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX, NULL); if (rc != SQLITE_OK) { // 处理打开失败的情况 } // 设置超时时间为5秒(单位为毫秒) sqlite3_busy_timeout(db, 5000); ``` 上述 C 代码展示了如何配置超时时间[^2]。这使得应用程序能够在指定时间内反复尝试获取锁,从而提高成功率。 --- #### 3. 自定义 BUSY 回调函数 除了简单的超时机制外,还可以实现自定义回调逻辑以更好地控制重试行为。例如,可以根据业务需求动态决定何时放弃重试。 ```c static int busyCallback(void *notUsed, int count) { if (count >= 10) { // 如果已经重试超过10次,则停止 return 0; // 返回0表示不再重试 } usleep(10000); // 否则休眠一段时间后再试一次 return 1; // 返回非零值表示继续重试 } sqlite3* db; sqlite3_open("example.db", &db); sqlite3_busy_handler(db, busyCallback, nullptr); ``` 这段代码实现了更加灵活的忙状态处理策略[^4]。 --- #### 4. 避免过多并发写入请求 由于 SQLite 是基于库级别的锁设计,因此在同一时刻仅能有一个活动的写事务。为了防止频繁发生 `SQLITE_BUSY` 错误,应尽量减少不必要的多线程/进程间的竞争情况。具体措施包括但不限于批量提交更新操作而不是逐条记录单独修改;或者考虑引入队列管理器统一调度所有针对该资源的操作流程[^3]。 --- #### 5. 调整应用架构适应单写多读特性 鉴于 SQLite 不支持真正的并行写入功能这一局限性,在某些高性能场景下可能需要重新评估是否适合采用它作为主要存储引擎。对于那些确实存在大量高频率随机写入需求的应用场合来说,或许迁移到具备更强并发能力的关系型数据库系统(如 PostgreSQL 或 MySQL)更为合适一些。 --- ### 总结 综上所述,解决 `SQLITE_BUSY` 问题可以从以下几个方面入手:优化锁机制(比如开启 WAL)、合理设定超时期限、编写定制化的忙碌响应算法以及改善整体软件结构使之契合 SQLite 单一写者的约束条件等等[^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值