SQLite库级锁简介和“database is locked”异常的解决方法

SQLite 是一个软件库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite允许多个进程/线程同时进行读操作,但在同一时刻只允许一个线程进行写操作。SQLite在进行写操作时,数据库文件会被锁定,此时任何其他的读/写操作都会被阻塞,如果阻塞超过5秒钟(默认是5秒,可通过重新编译SQLite进行修改),就会抛出描述为“database is locked”的异常。

出现上述现象的原因是SQLite只支持库级锁,不支持并发执行写操作,即使是不同的表,同一时刻也只能进行一个写操作。例如,事务T1在表A新插入一条数据,事务T2在表B中更新一条已存在的数据,这两个操作是不能同时进行的,只能顺序进行。

SQLite尽量延迟了申请X锁,直到数据块真正写盘时才申请X锁,再加上被阻塞的操作有等待时间,所以当SQLite作为客户端嵌入数据库被使用时时,一般情况下不会抛出“database is locked”的异常。但是,在高并发的环境下,还是很有可能抛出异常的。避免这种异常的最简单有效的方法,就是在进行写操作时实现互斥锁,并保证写操作按顺序执行。

下面是C#代码中的简单示例,UpdateData是用于执行数据库操作的函数。

try
{
    lock (sqlservice)
    {
        bool result = sqlservice.UpdateData(out error, sqlCmd);
        if (!result || !string.IsNullOrEmpty(error))
        {
            return false;
        }

        return true;
    }
}
catch (Exception ex)
{
    return false;
}

为了进一步优化高并发环境下SQLite的写操作,避免造成死锁,可以使用Monitor类实现对数据库文件的资源调度,.lock关键字实际上就是对Monitor对象进行封装,给object加上了一个互斥锁。比如使用Monitor.Wait()和Monitor.Pulse()函数在多线程环境下对资源所有权进行合理分配,在线程A的lock代码里调用Monitor.Wait(),会放弃对资源的占用,让线程B可以占用该资源。然后在占用资源的线程B的代码里调用Monitor.Pulse(),让线程A进入等待队列。线程B完成操作后,再调用Monitor.Wait()释放资源,这样线程A就可以继续执行了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章