sqlite遇到database is locked问题的完美解决方案

这两天在项目中用大强度大频率的方法测试时遇到sqlite报database is locked的问题,
分析下来原因是sqlite对数据库做修改操作时会做(文件)锁使得其它进程同一时间使用时会报该错误(也就是SQLITE_BUSY),但如果仅是多进程或多线程查询sqlite是支持的。
解决方法有:
1。使用进程或线程间的同步机制以避免同时操作;如用信号量,互斥锁等(pthread_mutex_lock,pthread_mutex_unlock),如果你的项目工程较大要求较高的话建议用此方法自行封装函数处理同步
2。使用sqlite提供的两个busy handler函数,但对于一个连接来说,只能有一个busy handle,两个函数会相互影响,设置一个的同时会清除另一个,应根据需要来选择。
int sqlite3_busy_handler(sqlite3 , int ()(void , int), void )
不注册此函数时默认回调函数为NULL,清除busy handle,申请不到锁直接返回;
函数可以定义一个回调函数,当出现数据库忙时sqlite会调用该函数进行延时并返回非0会重试本次操作,回调函数的第二个参数会被传递为此次因BUSY忙事件而调用该函数的次数,因此你完全可以自行控制多少次后(也就是延时多少后)才真正返回BUSY;
回调函数返回非0,数据库会重试当前操作,返回0则当前操作返回SQLITE_BUSY;

int sqlite3_busy_timeout(sqlite3*, int ms);
不注册此函数时默认超时等待为0,当ms<=0时,清除busy handle,申请不到锁直接返回;
定义一个毫秒数,当未到达该毫秒数时,sqlite会sleep并重试当前操作,
如果超过ms毫秒,仍然申请不到需要的锁,当前操作返回SQLITE_BUSY;
很多人用这个函数没有成功,其实只要你仔细查看sqlite的源码就会发现,
这个函数实际上注册了一个默认的sqlite3_busy_handler(sqliteDefaultBusyCallback),而这个回调函数在你的编译环境下可能使得第二个ms参数必需要大于1000且是他的整数倍才有意义,由于此默认callback函数延时较大,建议自己写回调函数然后用slite3_busy_handler注册,这样就可以自己用自己的延时函数或方法进行处理了.

附:===================================================================static int sqliteDefaultBusyCallback(
void ptr, / Database connection */
int count /* Number of times table has been busy */
){#if SQLITE_OS_WIN || (defined(HAVE_USLEEP) && HAVE_USLEEP)
static const u8 delays[] =
{ 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 };
static const u8 totals[] =
{ 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228 };

define NDELAY (sizeof(delays)/sizeof(delays[0]))

sqlite3 db = (sqlite3 )ptr;
int timeout = db->busyTimeout;
int delay, prior; assert( count>=0 );
if( count < NDELAY ){
delay = delays[count];
prior = totals[count];
}else{
delay = delays[NDELAY-1];
prior = totals[NDELAY-1] + delay*(count-(NDELAY-1));
}
if( prior + delay > timeout ){
delay = timeout - prior;
if( delay<=0 ) return 0;
}
sqlite3OsSleep(db->pVfs, delay*1000);
return 1;

else sqlite3 db = (sqlite3 )ptr;

int timeout = ((sqlite3 *)ptr)->busyTimeout;
if( (count+1)*1000 > timeout ){
return 0;//1000>timeout,so timeout must bigger than 1000
}
sqlite3OsSleep(db->pVfs, 1000000);//1000ms return 1;#endif} int sqlite3_busy_timeout(sqlite3 *db, int ms){
if( ms>0 ){
db->busyTimeout = ms;
sqlite3_busy_handler(db, sqliteDefaultBusyCallback, (void*)db);
}else{
sqlite3_busy_handler(db, 0, 0);
}
return SQLITE_OK;
} 3、解决方法二加上一个循环判断。
while( 1 )
{
if( SQLITE_OK != sqlite3_exec( myconn, sql, 0, 0, &m_sqlerr_msg) )
{
if( strstr(m_sqlerr_msg, “database is locked”) )
{
sleep(1);
continue;
}
break;
}
}
4、解决方法三
用信号量做PV操作
sem_p(semid,0);
sqlite3_exec( myconn, sql, 0, 0, &m_sqlerr_msg);
sem_v(semid,0);

转自:http://www.360doc.com/content/10/1214/12/87000_77984300.shtml

<think>好的,用户遇到了SQLite数据库被定的问题,需要解决方案。首先,我得回忆一下常见的导致这个问题的原因。SQLite是文件型数据库,同一时间只能有一个操作,如果有多个连接或进程同时尝试入,就会触发“database is locked”错误。 根据用户提供的引用内容,引用[1]提到用循环判断和重试的方法,即在执行数据库操作时,如果检测到定错误,就等待一段时间后重试。这种方法适用于并发操作较多的情况,通过重试机制避免直接失败。不过需要设置合理的等待时间,否则可能影响性能或者导致无限循环。 引用[2]则建议添加读,通过同步机制控制并发访问。在Go语言中使用sync.RWMutex可以有效地管理读操作,确保同一时间只有一个操作,而读操作可以并发进行。这样可以避免多个操作同时竞争数据库导致的问题。不过,这种方法需要修改代码结构,确保所有数据库访问都正确加,可能会增加代码复杂度。 另外,除了用户提供的解决方案,还有其他常见的方法需要考虑。比如,检查事务是否正确关闭,长时间未提交的事务会导致数据库一直被定。优化事务的使用,尽量缩短事务的时间,及时提交或回滚事务,可以减少定的概率。 还有,设置适当的忙等待超时时间,使用`sqlite3_busy_timeout`函数可以让SQLite在遇到定时自动重试一段时间,而不是立即返回错误。这种方法比较简单,但需要根据实际情况调整超时时间。 另外,使用WAL(Write-Ahead Logging)模式也是一个有效的方法。WAL模式允许读和同时进行,减少了冲突的可能性。启用WAL可以通过执行`PRAGMA journal_mode=WAL;`来实现,这可能需要在连接数据库时进行配置。 还需要考虑是否有其他应用程序或进程在访问数据库文件,比如备份工具、杀毒软件等,这些可能会意外定数据库文件,导致问题。确保数据库文件不被其他程序占用也是重要的。 总结一下,用户的问题需要综合多种解决方案。首先检查代码中的事务管理和连接关闭是否正确,然后考虑使用重试机制、读、设置忙等待超时,以及启用WAL模式。根据具体的应用场景和编程语言选择合适的方案,比如在Go中可以使用读,而在其他语言中可以结合重试和超时设置。</think>### SQLite数据库问题解决方案SQLite出现“database is locked”错误时,通常是由于多个连接或进程同时尝试入数据库导致的。以下是逐步解决方案: --- #### **1. 检查事务与连接管理** - **及时提交事务**:确保所有操作后正确提交事务,避免长时间未提交的事务占用。 ```python # 示例:Python中使用事务 with sqlite3.connect('test.db') as conn: cursor = conn.cursor() cursor.execute("BEGIN TRANSACTION") cursor.execute("INSERT INTO table VALUES (...)") conn.commit() # 显式提交 ``` - **关闭闲置连接**:使用完数据库后立即关闭连接,防止连接池残留导致竞争[^1]。 --- #### **2. 添加重试机制** 当检测到定时,通过循环等待并重试操作(如引用[1]所示): ```python import time import sqlite3 def safe_execute(sql): while True: try: conn = sqlite3.connect('test.db') cursor = conn.cursor() cursor.execute(sql) conn.commit() break except sqlite3.OperationalError as e: if "database is locked" in str(e): time.sleep(0.1) # 等待100ms后重试 continue else: raise e finally: conn.close() ``` --- #### **3. 使用读同步访问** 在代码层面控制并发(如引用[2]的Go语言示例): - **读分离**:操作时独占,读操作共享。 ```go // Go语言示例 var mutex sync.RWMutex func WriteData() { mutex.Lock() defer mutex.Unlock() // 执行操作 } func ReadData() { mutex.RLock() defer mutex.RUnlock() // 执行读操作 } ``` --- #### **4. 调整SQLite配置** - **设置忙等待超时**:通过`sqlite3_busy_timeout`设置超时时间(单位:毫秒),让SQLite自动重试。 ```c // C语言示例 sqlite3_busy_timeout(db, 5000); // 超时5秒 ``` - **启用WAL模式**:使用Write-Ahead Logging模式减少冲突。 ```sql PRAGMA journal_mode=WAL; ``` --- #### **5. 排查外部进程干扰** - 检查是否有其他程序(如文件同步工具、杀毒软件)正在访问数据库文件。 - 确保数据库文件权限正确,避免多用户同时入。 --- ### 总结方案选择 | 场景 | 推荐方案 | |------|----------| | 高并发入 | 读 + WAL模式 | | 短时定 | 重试机制 + 忙等待超时 | | 外部干扰 | 关闭无关进程 + 文件权限检查 | --- 相关问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值