最近项目中涉及到sqlite并发读写的问题,参考一些文档并结合本身的实践,对sqlite3并发问题总结了几点:html
sqlite3的锁及事务类型
sqlite3总共有三种事务类型:BEGIN [DEFERRED /IMMEDIATE / EXCLUSIVE] TRANSCATION,五种锁,按锁的级别依次是:UNLOCKED /SHARED /RESERVERD /PENDING /EXCLUSIVE。当执行select即读操做时,须要获取到SHARED锁(共享锁),当执行insert/update/delete操做(即内存写操做时),须要进一步获取到RESERVERD锁(保留锁),当进行commit操做(即磁盘写操做时),须要进一步获取到EXCLUSIVE锁(排它锁)。
对于RESERVERD锁,sqlite3保证同一时间只有一个链接能够获取到保留锁,也就是同一时间只有一个链接能够写数据库(内存),可是其它链接仍然能够获取SHARED锁,也就是其它链接仍然能够进行读操做(这里能够认为写操做只是对磁盘数据的一分内存拷贝进行修改,并不影响读操做)。
对于EXCLUSIVE锁,是比保留锁更为严格的一种锁,在须要把修改写入磁盘即commit时须要在保留锁/未决锁的基础上进一步获取到排他锁,顾名思义,排他锁排斥任何其它类型的锁,即便是SHARED锁也不行,因此,在一个链接进行commit时,其它链接是不能作任何操做的(包括读)。
PENDING锁(即未决锁),则是比较特殊的一种锁,它能够容许已获取到SHARED锁的事务继续进行,但不容许其它链接再获取SHARED锁,当已存在的SHARED锁都被释放后(事务执行完成),持有未决锁的事务就能够得到commit的机会了。sqlite3使用这种锁来防止writer starvation(写饿死)。
死锁的状况
死锁的状况:当两个链接使用begin transaction开始事务时,第一个链接执行了一次select操做(已经获取到SHARED锁),第二个链接执行了一次insert操做(已经获取到了RESERVERD锁),此时第一个链接须要进行一次insert/update/delete(须要获取到RESERVERD锁),第二个链接则但愿执行commit(须要获取到EXCLUSIVE锁),因为第二个链接已经获取到了RESERVERD锁,根据RESERVERD锁同一时间只有一个链接能够获取的特性,第一个链接获取RESERVERD锁的操做一定失败,而因为第一个链接已经获取到SHARED锁,第二个链接但愿进一步获取到EXCLUSIVE锁的操做也一定失败。就致使了事务死锁。
事务类型的使用原则
在用”begin transaction”显式开启一个事务时,默认的事务类型为DEFERRED,锁的状态为UNLOCKED,即不获取任何锁,若是在使用的数据库没有其它的链接,用begin就能够了。若是有多个链接都须要对数据库进行写操做,那就得使用BEGIN IMMEDIATE/EXCLUSIVE开始事务了。
使用事务的好处是:1.一个事务的全部操做至关于一次原子操做,若是其中某一步失败,能够经过回滚来撤销以前全部的操做,只有当全部操做都成功时,才进行commit,保证了操做的原子特性;2.对于屡次的数据库操做,若是咱们但愿提升数据查询或更新的速度,能够在开始操做前显式开启一个事务,在执行完全部操做后,再经过一次commit来提交全部的修改或结束事务。
对SQLITE_BUSY的处理
当有多个链接同时对数据库进行写操做时,根据事务类型的使用原则,咱们在每一个链接中用BEGIN IMMEDIATE开始事务,即多个链接都尝试取得保留锁的状况,根据保留锁同一时间只有一个链接能够获取到的特性,其它链接都将获取失败,即事务开始失败,这种状况下,sqlite3将返回一个SQLITE_BUSY的错误,若是咱们不但愿操做就此失败而返回,就必须处理SQLITE_BUSY的状况,sqlite3提供了sqlite3_busy_handler或sqlite3_busy_timeout来处理SQLITE_BUSY,对于sqlite3_busy_handler,咱们能够指定一个busy_handler来处理,并能够指定失败重试的次数。而sqlite3_busy_timeout则是由sqlite3自动进行sleep并重试,当sleep的累积时间超过指定的超时时间时,最终返回SQLITE_BUSY。须要注意的是,这两个函数同时只能使用一个,后面的调用会覆盖掉前次调用。从使用上来讲,sqlite3_busy_timeout更易用一些,只须要指定一个总的超时时间,而后sqlite本身会决定多久进行重试以及重试的次数,直到达到总的超时时间最终返回SQLITE_BUSY。而且,这两个函数一经调用,对其后的全部数据库操做都有效,很是方便。
参考: