Sqlite3锁说明及如何避免锁

1、sqlite3简介

为什么要说sqlite3,因为很多追求轻量级存储的开发者在使用它,尤其是在移动端及嵌入式系统,主要是由于存储空间的限制。包括微信最初也是用的sqlite,并对sqlite做了优化,基于此推出了自己的库WCDB,并且已经开源,目前有9k+加星。
sqlite的轻量级和使用简单成就了它,当然河有两岸,事有两面,sqlite也放弃了一些其他主流关系型数据库的功能,比如行锁、安全性等。所以sqlite使用起来需要注意些小细节,比如锁问题。
我知道sqlite是粒度很粗的锁,针对的是整个DB文件,但是我在使用sqlite过程中,没有遇到过锁库的问题,也正是因此,在写程序过程中总是提着一根弦,所以我想一探究竟。

2、sqlite3事务及锁说明

2.1、事务

sqlite为啥要有锁?因为它要支持事务,事务是数据库状态转移的基本单位。
事务有ACID的特性,即原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)。这里原子性和持久性比较好理解,隔离性也还好,一致性就比较抽象了。
一致性是这样解释的:

Consistency ensures that a transaction can only bring the database
from one valid state to another, maintaining database invariants: any
data written to the database must be valid according to all defined
rules, including constraints, cascades,triggers, and any combination
thereof. This prevents database corruption by an illegal transaction,
but does not guarantee that a transaction is correct.

就是说,一致性确保数据库在一些规定下从一个有效状态,变换到另一个有效的状态。一致性有两个层面

  • 第一个是数据库层面,数据库内部需要基于一些约束规则,来检测数据是否违反了一致性的约束,比如外键约束和唯一性约束等。

  • 另一个是应用层的业务逻辑

我看到网上有人在争吵,A说B解释的一致性就是在说原子性,B说我就是在说一致性。。。
我认为 原子性也好一致性也罢,还有持久性,都是在从不同的视角看事务这个东西。就像从不同角度看人,难免会有些像。

BEGIN;
SELECT * FROM ACCOUNT;
UPDATE ACCOUNT SET money = 1000 WHERE username = ‘heisu’;
UPDATE ACCOUNT SET balance = balance + 1000 WHERE username = ‘heisu’;
COMMIT;

以上是一个事务,要从技术的角度看事务,否则容易把简单的东西复杂化:
从 BEGIN 到 COMMIT 要么就执行完,要么就不执行就是他的原子性
能够正确的从BEGIN 执行到COMMIT ,就是事务的一致性(部分,事务一致性的数据一致性的部分)

我觉得把一致性和另外三个特性放在一起说事务的特性很奇怪,其他三个都可以从技术上去感受,但是一致性在一定程度上脱离了数据库本身,涉及了业务逻辑就比较怪。

2.2、锁

sqlite的锁是文件锁,作用的是整个DB文件,同一时间可以有多个读事务,但是同一时间最多只能有一个写事务;
sqlite有四个类型的锁,很重要

  • 未决锁(PENDING_LOCK):

    一种过度锁

  • 共享锁(SHARED_LOCK):

    用于通知其他连接,数据库属于读状态。多连接可以同时获得并持有共享锁,即多连接可以同时读,共享锁未释放时,不允许写数据。

  • 保留锁(RESERVED_LOCK):

    写数据库需要获取保留锁,数据库只允许有以一个保留锁。保留锁是写数据库的第1 阶段。保留锁不会阻止拥有共享锁的连接继续读数据库,也不阻止其它连接获得新的共享锁。 一旦一个连接获得了保留锁,它就可以开始处理数据库修改操作了(在缓冲区)。

  • 排它锁(EXCLUSIVE_LOCK):

    提交事务时,需要将保留锁提升为排它锁。为了得到排它锁,必须首先将保留锁提升为未决锁。获得未决锁之后,其它连接就不能再获得新的共享锁了,但已经拥有共享锁的连接仍然可以继续正常读数据库。此时,拥有未决锁的连接等待其它拥有共享锁的连接完成工作并释放其共享锁。 一旦所有其它共享锁都被释放,拥有未决锁的连接就可以将其锁提升至排它锁,此时就可以自由地对数据库进行修改了。所有以前对缓冲区所做的修改都会被写到数据库文件。

一共有五个状态(加上UNLOCKED)
锁在事务进行的过程中进行转化Sqlite3锁说明及如何避免锁_第1张图片
锁从事务开始的时候进行转换,BEGIN 标识事务开始,直到COMMIT标识事务结束,如果在事务过程中出错,则回滚ROLLBACK。

事务有多种类型,在BEGIN 命令中指定: BEGIN [ DEFERRED | IMMEDIATE | EXCLUSIVE ] TRANSACTION,他们在事务开始时所获取的锁不同。在使用sqlite3接口时会根据生成库时的配置模式判断事务类型。

为什么需要未决锁和保留锁?

  • 未决锁作用主要是为了防止写饿死的情况,写事务获取Pending锁后,新的读事务无法再进来,然后再加EXCLUSIVE锁,这样写事务获取锁的几率大大提高。
  • 保留锁的目的是为了提高并发。由于SQLite只有库级排斥锁,如果写事务一开始就上排斥锁,然后再进行实际的数据更新,写磁盘操作,这会使得并发性大大降低。而SQLite一旦得到数据库的RESERVED锁,就可以对缓存中的数据进行修改,而与此同时,其它进程可以继续进行读操作。直到真正需要写磁盘时才对数据库加排斥锁。

3、如何避免数据库读写冲突(锁)

  • 写进程和写进程间一定会阻塞
  • 读和写之间在有的情况下会阻塞(如写的时候读,或者读完未正确close连接然后写),有的情况下不会阻塞(如开启wal模式)
  • 读和读之间不阻塞

如何避免写之间冲突?
可以将所有的写任务串行化,使用任务队列,使用一个线程单独处理所有的写。也可以sql和数据库的open,close做在一个接口中,避免过长的持有资源。
如果避免读写间冲突?
可以使用wal模式,wal模式下写不会阻塞读。

如果不是高效sql的化不必太担心读写冲突,因为在sqlite中有超时处理:即如果一个sql进程发现自己被阻塞了,会等待一段时间(默认5s),如果在此时间内资源被释放,则可以继续执行事务,如果超过超时时间,则报busy。

所以大多情况下不会阻塞

4、拓展

高性能模式-wal模式
wal会产生两个文件,一个.shm,一个.wal文件,生命周期是从数据库的open 到close。
什么是 WAL|数据库性能

参考:
1、如何理解数据库事务中的一致性的概念?
2、数据库SQLite的使用和优化
3、什么是事务的一致性?一致性和原子性的区别是什么?
4、SQLite锁机制简介
5、sqlite 超时时间设置

你可能感兴趣的:(sqlite,数据库,database)