[转载]sqlite3遇到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,             
 int count              
)
{
#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);
--------------------------------------------------------------------------------------------------
int sqlite3_busy_timeout(sqlite3* , int ms) ;
这个例程的设置忙处理程序为指定的时间量sleeps 当表已被锁定处理程序将睡眠多次至少"ms "毫秒的休眠之前, 布尔型至少"ms "毫秒后处理程序的休眠, 返回0, 导致sqlite3_step( )返回SQLITE_BUSY 或SQLITE_IOERR_BLOCKED .
调用这个例程, 用参数小于或等于零将关闭全部忙碌处理程序.
只能存在一个忙特定的处理程序数据库连接任何任何给定时刻如果另一个忙处理程序被定义(使用sqlite3_busy_handler( )调用这个例程之前), 其它忙处理程序被清除.
--------------------------------------------------------------------------------------------------
 
SQLite数据库在使用的过程中经常发生的数据库异常便是数据库被锁定了(SQLITE_BUSY或者SQLITE_LOCKED)。SQLite对于并发的处理机制是允许同一个进程的多个线程同时读取一个数据库,但是任何时刻只允许一个线程/进程写入数据库。所以必须要对数据库的读写来进行控制。
  SQLite数据库本身用来处理锁定情况的两个函数:
  int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*);
  int sqlite3_busy_timeout(sqlite3*, int ms);
  使用这两个函数可以设定当发生数据库锁定的时候,调用什么函数来处理,以及设定锁定时的等待时间。
  当然通常情况下我们还可以用另外一种方法来解决这类问题,那便是设置互斥量。一般情况下我们可以使用互斥量,临界区等来实现。在这里我使用了互斥量,主要是因为我需要在多个不同的进程中并发的访问同一个数据库。用于锁定和解锁的函数如下:
  void Lock()
  {
  if((hCounter = OpenMutex(MUTEX_ALL_ACCESS,FALSE,"kangxiaofang")) == NULL)
  {
  //如果没有其他进程创建这个互斥量,则重新创建
  hCounter = CreateMutex(NULL,FALSE,"kangxiaofang");
  WaitForSingleObject(hCounter,INFINITE);
  }
  else
  {
  WaitForSingleObject(hCounter,INFINITE);
  }
  }
  void UnLock()
  {
  //释放使用权
  ReleaseMutex(hCounter);
  //关闭句柄
  CloseHandle(hCounter);
  }
  当然我们也可以把等待时间设定为60秒,以免出现死等的现象。具体使用时可以按如下调用:
  //锁定数据库
  Lock();
  rc = sqlite3_exec(db, "BEGIN;", 0, 0, &zErrMsg);
  rc = sqlite3_exec(db, exec, 0, 0, &zErrMsg);
  rc = sqlite3_exec(db, "COMMIT;", 0, 0, &zErrMsg);
  //数据库解锁
  UnLock();
  总结:
  经过测试后,你会发现这种方法所需的消耗要小于循环打开数据库的操作。(王朝网络 wangchao.net.cn)

posted @ 2016-05-23 14:36  陈晓涛  阅读(1854)  评论(0编辑  收藏  举报