SQLite锁机制

3,557 阅读6分钟

SQLite有一个加锁表,用来帮助不同的写数据库都能够在最后一刻加锁,保证最大的并发性。 SQLite有五种不同的锁状态:

  • unlocked:最初状态,未加锁,连接还没有开始访问数据库
  • shared:共享锁,多个连接可以同时获得并保持shared锁,即多个连接可以同时从同一个数据库中读数据。
  • reserved:保留锁,一个连接如果想要写数据库,必须首先获得一个reserved锁。一个数据库同时只能有一个reserved锁,该reserved锁可以与shared锁共存,它是写数据库的第一个阶段。reserved锁既不阻止其他拥有shared锁的连接继续读数据库,也不阻止其他连接获得新的shared锁。当一个连接获得了reserved锁,就可以开始处理数据库修改操作了。尽管这些修改只能在缓冲区进行,而不是实际写到磁盘,修改保存在内存缓冲区中。
  • pending:未决锁,当连接想要提交修改时,需要将reserved锁提升为pending锁。获得pending锁后,其他连接就不能再获得新的共享锁了,但已经拥有shared锁的连接仍然可以继续正常读数据库。此时,拥有reserved锁的连接等待其他拥有shared锁的连接完成工作并释放其共享锁。
  • exclusive:排它锁,一旦所有的其他共享锁都被释放,拥有pending锁的连接就可以将锁提升至exclusive锁,此时就可以自由地对数据库进行修改。所有以前所缓存的修改都会被写到数据库文件中。

SQLite事务

事务定义了一组SQL命令的边界,这组命令或者作为一个整体被全部执行,或者都不执行,这被称为数据库完整性的原子性原则。 默认情况下,SQLite中每条SQL语句自成事务(自动提交模式)。所有成功完成的命令偶都自动提交,所有遇见错误的命令都回滚。

事务属性:

  • 原子性:保证任务中的所有操作都执行完毕;否则,事务会在出现错误时终止,并回滚之前所有操作到原始状态。
  • 一致性:如果事务成功执行,则数据库的状态得到了进行了正确的转变。
  • 隔离性:保证不同的事务相互独立、透明地执行。
  • 持久性:即使出现系统故障,之前成功执行的事务的结果也会持久存在。

默认情况下,SQLite的每条语句就是一条事务,比如执行一条INSERT语句,执行后,INSERT语句就生效,提交到了数据库中。

SQLite使用以下命令控制事务:

  • BEGIN TRNSACTION --开始事务
  • COMMIT --提交,也可以使用END TRANSACTION命令
  • ROLLBACK --回滚

SQLite事务类型

SQLite有三种不同的事务类型:

  • DEFERRED(推迟)
  • MMEDIATE(立即)
  • EXCLUSIVE(排它)

事务类型在BEGIN命令中指定。

一个deferred事务不获取任何锁,直到它需要锁的时候。而且BEGIN语句本身也不会做什么事情——它开始于UNLOCK状态;默认情况下是这样的。如果仅仅用BEGIN开始一个事务,那么事务就是DEFERRED的,同时它不会获取任何锁,当对数据库进行第一次读操作时,它会获取SHARED LOCK;同样,当进行第一次写操作时,它会获取RESERVED LOCK。

由BEGIN开始的Immediate事务会试着获取RESERVED LOCK。如果成功,BEGIN IMMEDIATE保证没有别的连接可以写数据库。但是,别的连接可以对数据库进行读操作,但是RESERVED LOCK会阻止其它的连接BEGIN IMMEDIATE或者BEGIN EXCLUSIVE命令,SQLite会返回SQLITE_BUSY错误。这时你就可以对数据库进行修改操作,但是你不能提交,当你COMMIT时,会返回SQLITE_BUSY错误,这意味着还有其它的读事务没有完成,得等它们执行完后才能提交事务。

Exclusive事务会试着获取对数据库的EXCLUSIVE锁。这与IMMEDIATE类似,但是一旦成功,EXCLUSIVE事务保证没有其它的连接,所以就可对数据库进行读写操作了。 上面那个例子的问题在于两个连接最终都想写数据库,但是他们都没有放弃各自原来的锁,最终,shared 锁导致了问题的出现。如果两个连接都以BEGIN IMMEDIATE开始事务,那么死锁就不会发生。在这种情况下,在同一时刻只能有一个连接进入BEGIN IMMEDIATE,其它的连接就得等待。BEGIN IMMEDIATE和BEGIN EXCLUSIVE通常被写事务使用。就像同步机制一样,它防止了死锁的产生。 基本的准则是:如果你在使用的数据库没有其它的连接,用BEGIN就足够了。但是,如果你使用的数据库在其它的连接也要对数据库进行写操作,就得使用BEGIN IMMEDIATE或BEGIN EXCLUSIVE开始你的事务

SQLite死锁

SQLite可能会发生死锁。如:

B连接执行INSERT语句时获得了reserved锁,reserved锁只有一个,而且它不会阻止其他连接获取shared锁;A连接执行SELECT语句获得shared锁;此时B连接进行commit,由于shared锁还未释放,B连接无法获得pending锁;A连接执行INSERT语句,想获取reserved锁,但是reserved锁只有一个且被B连接持有,所以A连接等待reserved锁;A连接shared锁一直没有释放,B连接就不能提升到exclusive锁,等待A连接释放shared锁。

使用正确的事务类型可以避免死锁。

一个DEFERRED事务不获取任何锁(直到它需要锁的时候),BEGIN语句本身也不会做什么事情——它开始于UNLOCK状态。默认情况下就 是这样的,如果仅仅用BEGIN开始一个事务,那么事务就是DEFERRED的,同时它不会获取任何锁;当对数据库进行第一次读操作时,它会获取 SHARED锁;同样,当进行第一次写操作时,它会获取RESERVED锁。

由BEGIN开始的IMMEDIATE事务会尝试获取RESERVED锁。如果成功,BEGIN IMMEDIATE保证没有别的连接可以写数据库。但是,别的连接可以对数据库进行读操作;但是,RESERVED锁会阻止其它连接的BEGIN IMMEDIATE或者BEGIN EXCLUSIVE命令,当其它连接执行上述命令时,会返回SQLITE_BUSY 错误。这时你就可以对数据库进行修改操作了,但是你还不能提交,当你 COMMIT时,会返回SQLITE_BUSY错误,这意味着还有其它的读事务没有完成,得等它们执行完后才能提交事务。

EXCLUSIVE事务会试着获取对数据库的EXCLUSIVE锁。这与IMMEDIATE类似,但是一旦成功,EXCLUSIVE事务保证没有其它的连接,所以就可对数据库进行读写操作了。