概述

SQLite虽然是一个轻量的嵌入式数据库,但这并不影响它支持事务。所谓支持事务,即需要在并发环境下,保持事务的ACID特性。事务的原子性,隔离性都需要通过并发控制来保证。那么Sqlite的并发控制是怎样的,如何实现,在这里跟大家分享下我的理解。

SQLite是一个文件数据库,所有的数据都在一个db文件中,对于wal模式,还包含wal索引文件和wal日志文件。SQlite支持库级并发,即允许多个读事务同时运行,同一时刻最多只有一个写事务,读写冲突,相对于传统的DBMS支持表级,行级甚至MVCC,SQLite的库级并发确实显得比较寒碜。但是锁粒度越细,意味着维护锁的成本越高,系统也会越复杂,因此SQLite的封锁机制要简单很多,对资源的消耗也非常少。SQLite 3.7版本后,对并发控制做了优化,推出了WAL日志模式,可以实现读写并发,但同一个时刻仍然只能有一个写事务。由于SQLite的实现方式,SQLite只支持两种隔离级别,串行化和读未提交。读未提交,就是读全程不上锁;串行化在事务开启时上读锁,上锁和释放锁同样遵守两阶段锁协议,在事务提交或回滚时才释放锁。

文件锁

要说清楚SQLite锁实现机制,首先要了解文件锁,因为SQLite所有锁实现都是基于文件锁。对于Linux系统,文件锁主要包含两类,协同锁和强制锁,协同锁类似于互斥量,需要参与者都遵守游戏规则,在操作文件前,都先上锁,而强制锁由OS内核强制实行。协同锁根据锁粒度分为文件级别和范围级别。锁文件是最简单的对文件加锁的方法,每个需要加锁的数据文件都有一个锁文件(lock file)。当锁文件存在时,就认为该数据文件已经被加锁,别的进程不应该访问。当锁不存在,进程就可以创建一个锁文件,然后访问相应的数据文件。只要创建锁的过程是原子的,就能保证某一时刻只有一个进程拥有该锁,这种方法保证某一时刻只有一个进程访问文件。文件锁的弊端显而易见,并发粒度太低。范围锁相对于文件锁,可以锁文件的一部分内容,并且有读锁和写锁。对于同一部分内容,读锁可以共存,读锁和写锁互斥。POSIX标准提供接口fcntl()来实现。

锁类型

SQLite中的锁正是利用了范围锁来实现并发控制的目的。SQLite中主要包含了4种锁:共享锁(SHARED_LOCK)、保留锁(RESERVED_LOCK)、未决锁(PENDING_LOCK)和排它锁(EXCLUSIVE_LOCK),这4种锁定义了3个区域,其中共享锁和排它锁占用文件相同的区域。具体而言,SQLite定义了文件的以下区域为锁文件区域,由于fcntl可以对不存在的文件区域加锁,因此 PENDING_BYTE定位在区域1G的地方,即使DB文件没这么大也不影响。三种类型的锁,分别在1G,1G+1,1G+2的偏移处,之所以SHARED_SIZE长度是510,原因在于windows环境下,LockFile()加锁区域不能重叠(Linux没有这种问题),对于同一个字节上锁会影响并发,因此设置了一个范围,对SHARED_FIRST—SHARED_FIRST+ SHARED_SIZE范围内的随机数进行加锁,这样可以减少冲突,保证高效的读取文件。具体锁类别和说明参见表1

锁类别

字节范围

说明

PENDING_BYTE

0x40000000

一种过渡锁,读事务获取读锁,写事务获取写锁前,都需要获取该锁。

RESERVED_BYTE

0x40000001

表示线程要开始写操作,某一时刻只能有一个RESERVED Lock,但是RESERVED锁和SHARED锁可以共存,而且可以对数据库加新的SHARED锁。

SHARED_LOCK

0x40000002-0x40000200

共享锁,开启事务时,都需要获取该锁

EXCLUSIVE_LOCK

0x40000002-0x40000200

排它锁

表1

从各个锁的作用来看,不免会疑问,为啥要加上RESERVED_LOCK和PENDING_LOCK两种类型,直接通过共享锁和排它锁不就可以达到读读共享,读写互斥的目的了吗。这里引入这Reserved锁的目的是为了提高并发。由于SQLite只有库级排斥锁(EXCLUSIVE
LOCK),如果写事务一开始就上EXCLUSIVE锁,然后再进行实际的数据更新,写磁盘操作,这会使得并发性大大降低。而SQLite一旦得到数据库的RESERVED锁,就可以对缓存中的数据进行修改,而与此同时,其它进程可以继续进行读操作。直到真正需要写磁盘时才对数据库加EXCLUSIVE锁。Pending锁的作用主要是为了防止写饿死的情况,写事务获取Pending锁后,新的读事务无法再进来,然后再加EXCLUSIVE锁,这样写事务获取锁的几率大大提高,读写事务的流程如下表2,状态变迁图如图1。

类型

操作

锁信息

说明

读事务

begin

不持有锁

select c1 from
user where id=1

Lock: Pending(Read)

Lock:Shared(Read)

Unlock:Pending

获取Shared读锁前,需要先获取Pending共享锁,

通过这种方式与写事务互斥。

commit

UnLock:Shared

写事务

begin

Update c1=c1+1
where id=1

Lock: Pending(Read)

Lock:Shared

Unlock:Pending

Lock:Reserved(Write)

先获取Shared读锁,然后获取Reserved的排它锁,阻止其它写事务

commit

Lock:Pending(Write)

Lock:Exclusive(Write)

Unlock: Pending

Unlock:
Exclusive(Write)

获取Pending的排它锁,阻止新的读事务,最后上排它锁,阻止所有读事务,读写不能并发

Pending锁方式好处是,减少写饿死的几率。

表2

图1

Wal锁类型

引入WAL机制后,SQLite开始支持读写并发,并且引入了WAL日志文件锁。WAL日志锁实质是锁wal-index文件的区域,根据不同的锁类型,将wal-index文件的不同区域划定义成不同的锁,主要有读锁,写锁,检查点锁,具体如表3,4。WAL模式下,最新的数据位于日志文件中,无论是读事务还是写事务都需要持有WAL_READ_LOCK的读锁,因为它们都需要获取最新的事务点。因此,做检查点时,可以通过对WAL_READ_LOCK位置(124-127)上锁,来确定检查点需要等待还是停止推进。同时我们也可以看到,对于DB文件,读写事务都只需要对DB文件上读锁,对于WAL日志文件,WAL_READ_LOCK和WAL_WRITE_LOCK位于不同的位置,读写相互不影响,所以读写不互斥。

锁类别

字节范围

说明

读事务(WAL)

begin

select c1 from
user where id=1

DB文件:

Lock: Pending(Read)

Lock:Shared

Unlock:Pending

WAL文件:

Lock:WAL_READ_LOCK(Read)

除了获取DB文件锁,还需要获取WAL锁,得到最新提交事务的位点。

若有事务再作检查点,需要重试多次。

commit

Unlock:WAL_READ_LOCK

Unlock:Shared

写事务(WAL)

begin

Update c1=c1+1
where id=1

DB文件:

Lock:
Pending-Read

Lock:Shared(Read)

Unlock:Pending

WAL文件:

Lock:WAL_READ_LOCK(Read)

Lock:WAL_WRITE_LOCK(Write)

通过

EXCLUSIVE-WRITE-LOCK控制写写并发

由于不操作DB文件,因此不存在读写冲突,读写可以并发。

commit

WAL文件:

Lock:SHARED-READ-LOCK

Unlock:WAL_READ_LOCK(Read)

Unlock: WAL_WRITE_LOCK(Write)

DB文件:

Unlock:Shared

获取SHARED-READ-LOCK目的是为了获取最新提交日志的位点

检查点

操作

(WAL)

WAL文件:

Lock:WAL_CKPT_LOCK(write)

Lock:WAL_READ_LOCK(write)

UnLock:WAL_READ_LOCK

UnLock:WAL_CKPT_LOCK

EXCLUSIVE-CKPT-LOCK
保证只有一个写事务做检查点;

WAL_READ_LOCK阻止读写事务。

表3

锁类别

字节范围

说明

WAL_WRITE_LOCK

120

写锁位置

WAL_CKPT_LOCK

121

检查点锁位置

WAL_RECOVER_LOCK

122

故障恢复锁位置

WAL_READ_LOCK

123

读锁(表示不需要wal文件)

124-127

读锁(每个位置,对应一个锁)

做检查点时,逐一对每个位置上写锁,若上锁失败表示对应位置上的读事务没有结束,根据检查点策略确定是等待(FULL),还是停止推进(PASSIVE)。

表4

调试

SQLite通过几个宏定义可以打印语句执行的锁信息,方便大家了解语句执行中加了哪些锁,什么时候加的,什么时候释放的,以及如何处理锁冲突。具体的宏包括SQLITE_LOCK_TRACE,SQLITE_FORCE_OS_TRACE,和SQLITE_DEBUG,具体可以在代码中查看宏定义的注释。

gcc sqlite3.c -g -lpthread -ldl -fPIC -shared -DSQLITE_TEST -DSQLITE_DEBUG -DSQLITE_LOCK_TRACE -DSQLITE_FORCE_OS_TRACE -o libsqlite3.so

参考文档

http://my.oschina.net/u/587236/blog/129022

http://www.cnblogs.com/hustcat/archive/2009/03/01/1400757.html

Sqlite学习笔记(五)&&SQLite封锁机制的更多相关文章

  1. SQLite学习笔记(八)&&sqlite实现架构

    该系列的前面一些文章我重点讲了sqlite的核心功能,比如封锁机制,共享缓存,以及事务管理等.但对于sqlite的整体没有作一个全面的介绍,本文将从实现的层面,整体介绍sqlite的框架.各个核心模块 ...

  2. SQLite学习笔记(七)&&事务处理

    说到事务一定会提到ACID,所谓事务的原子性,一致性,隔离性和持久性.对于一个数据库而言,通常通过并发控制和故障恢复手段来保证事务在正常和异常情况下的ACID特性.sqlite也不例外,虽然简单,依然 ...

  3. SQLite 学习笔记

    SQLite 学习笔记. 一.SQLite 安装    访问http://www.sqlite.org/download.html下载对应的文件.    1.在 Windows 上安装 SQLite. ...

  4. Sqlite学习笔记(四)&&SQLite-WAL原理

    Sqlite学习笔记(三)&&WAL性能测试中列出了几种典型场景下WAL的性能数据,了解到WAL确实有性能优势,这篇文章将会详细分析WAL的原理,做到知其然,更要知其所以然. WAL是 ...

  5. Sqlite学习笔记(四)&&SQLite-WAL原理(转)

    Sqlite学习笔记(三)&&WAL性能测试中列出了几种典型场景下WAL的性能数据,了解到WAL确实有性能优势,这篇文章将会详细分析WAL的原理,做到知其然,更要知其所以然. WAL是 ...

  6. sqlite学习笔记7:C语言中使用sqlite之打开数据库

    数据库的基本内容前面都已经说得差点儿相同了.接下看看如何在C语言中使用sqlite. 一 接口 sqlite3_open(const char *filename, sqlite3 **ppDb) 打 ...

  7. C#可扩展编程之MEF学习笔记(五):MEF高级进阶

    好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用 ...

  8. (转)Qt Model/View 学习笔记 (五)——View 类

    Qt Model/View 学习笔记 (五) View 类 概念 在model/view架构中,view从model中获得数据项然后显示给用户.数据显示的方式不必与model提供的表示方式相同,可以与 ...

  9. java之jvm学习笔记五(实践写自己的类装载器)

    java之jvm学习笔记五(实践写自己的类装载器) 课程源码:http://download.csdn.net/detail/yfqnihao/4866501 前面第三和第四节我们一直在强调一句话,类 ...

随机推荐

  1. 泛函编程(4)-深入Scala函数类

    既然是泛函编程,多了解一下函数自然是免不了的了: 方法(Method)不等于函数(Function) 方法不是函数但可以转化成函数:可以手工转换或者由编译器(compiler)在适当的情况下自动转换. ...

  2. 内存溢出与jvm参数配置 ***最爱那水货

    第一类内存溢出,也是大家认为最多,第一反应认为是的内存溢出,就是堆栈溢出: 那什么样的情况就是堆栈溢出呢?当你看到下面的关键字的时候它就是堆栈溢出了: Java.lang.OutOfMemoryErr ...

  3. BZOJ 2467 解题报告

    对于一个合格的程序员来说,掌握一定的数学知识是非常必要的,所以这次就开个数学专题玩玩. 不多说啥,上题目,我们直接分析题目! 首先ORZ stonepage神犇,一眼就看出我把快速幂写成快速乘了…… ...

  4. jquery学习笔记:获取下拉框的值和下拉框的txt

    <div class="form-group"> <select class="form-control" id="iv_level ...

  5. spring MVC @Resource不支持Lazy加载

    今天迁一系统时发现有个bean使用@Resource注入了另外一个bean,这个被注入的bean是将被deprecated的类,而且只有一两个功能使用到,为了先调整进行测试,增加了@Lazy注解,启动 ...

  6. Hibernate(三)__核心接口和类

    该图显示了核心接口类以及配置文件的关系层次,越往下越偏向底层数据库. 1. hibernate.cfg.xml文件 ①该文件主要用于指定各个参数,是hibernate核心文件 ②默认放在src目录下, ...

  7. Context.js 右键菜单

    ContextJS is a lightweight solution for contextual menus. Currently, there are two versions. The fir ...

  8. 国外经典设计:12个漂亮的移动APP网站案例

    优秀的移动应用程序网站是设计灵感的重要来源.从美丽的图像,合理的使用空白到排版和颜色的使用,似乎设计师都加倍努力以创造一些美好和独特的设计来推广自己的应用程序. 因此,在这篇文章中,我们已经聚集了13 ...

  9. 【经验之谈】前端面试知识点总结(CSS相关)——附答案

    目录 二.CSS部分 1.解释一下CSS的盒子模型? 2.请你说说CSS选择器的类型有哪些,并举几个例子说明其用法? 3.请你说说CSS有什么特殊性?(优先级.计算特殊值) 4.要动态改变层中内容可以 ...

  10. win7系统下,vs2010一调式,vs就关闭要重启

    进入我的文档 %appdata%\Microsoft\VisualStudio, 将 10.0 重命名.网上找的方法有些问题,可能找这路径很难找到啊. 于是自己 找了找 一般都在当前用户文件夹下 Ap ...