- 浏览: 29530 次
- 性别:
- 来自: 广州
最新评论
写在前面:本节主要谈谈SQLite的锁机制,SQLite是基于锁来实现并发控制的,所以本节的内容实际上是属于事务处
理的,但是SQLite的锁机制实现非常的简单而巧妙,所以在这里单独讨论一下。如果真正理解了它,对整个事务的实现也就理解了。而要真正理解
SQLite的锁机制,最好方法就是阅读SQLite的源码,所以在阅读本文时,最好能结合源码。SQLite的锁机制很巧妙,尽管在本节中的源码中,我
写了很多注释,也是我个人在研究时的一点心得,但是我发现仅仅用言语,似乎不能把问题说清楚,只有通过体会,才能真正理解SQLite的锁机制。好了,下
面进入正题。
SQLite的并发控制机制是采用加锁的方式,实现非常简单,但也非常的巧妙,本节将对其进行一个详细
的解剖。请仔细阅读下图,它可以帮助更好的理解下面的
内容。
1、
RESERVED LOCK SQLite在pager层获取锁的函数如下:
在几个关键的部位标记数字。
(I)对于一个读事务会的完整经过: 注:
在上面的过程中,由于(1)的执行,使得某些时刻SQLite处于两种状态,但它持续的时间很短,从某种程度上来说可以忽略,但是为了把问题说清楚,在这
里描述了这一微妙而巧妙的过程。
4、
SQLite的死锁问题
5、
事务类型(Transaction Types)
所以应用程序应该尽量避免产生死锁,那么应用程序如何做可以避免死锁的产生呢?
RESERVED锁意味着进程将要对数据库进行写操作。某一时刻只能有一个
RESERVED Lock,但是RESERVED锁和SHARED锁可以共存,而且可以对数据库加新的SHARED锁。
为什么要用RESERVED锁?
主
要是出于并发性的考虑。
由于SQLite只有库级排斥锁(EXCLUSIVE
LOCK),如果写事务一开始就上EXCLUSIVE锁,然后再进行实际的数据更新,写磁盘操作,这会使得并发性大大降低。而SQLite一旦得到数据库
的RESERVED锁,就可以对缓存中的数据进行修改,而与此同时,其它进程可以继续进行读操作。直到真正需要写磁盘时才对数据库加EXCLUSIVE
锁。
2、PENDING LOCK
PENDING
LOCK意味着进程已经完成缓存中的数据修改,并想立即将更新写入磁盘。它将等待此时已经存在的读锁事务完成,但是不允许对数据库加新的SHARED
LOCK(这与RESERVED LOCK相区别)。
为什么要有PENDING
LOCK?
主要是为了防止出现写饿死的情况
。由于写事务先要获取RESERVED LOCK,所以可能一直产生新的SHARED
LOCK,使得写事务发生饿死的情况。
3、
加锁机制的具体实现
//获取一个文件的锁,如果忙则重复该操作,
//直到busy 回调用函数返回flase,或者成功获得锁
static int pager_wait_on_lock(Pager *pPager, int locktype){
int rc;
assert( PAGER_SHARED==SHARED_LOCK );
assert( PAGER_RESERVED==RESERVED_LOCK );
assert( PAGER_EXCLUSIVE==EXCLUSIVE_LOCK );
if( pPager->state>=locktype ){
rc = SQLITE_OK;
}else{
//重复直到获得锁
do {
rc = sqlite3OsLock(pPager->fd, locktype);
}while( rc==SQLITE_BUSY && sqlite3InvokeBusyHandler(pPager->pBusyHandler) );
if( rc==SQLITE_OK ){
//设置pager的状态
pPager->state = locktype;
}
}
return rc;
}
Windows下具体的实现如下:
Code
static int winLock(OsFile *id, int locktype){
int rc = SQLITE_OK; /* Return code from subroutines */
int res = 1; /* Result of a windows lock call */
int newLocktype; /* Set id->locktype to this value before exiting */
int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */
winFile *pFile = (winFile*)id;
assert( pFile!=0 );
TRACE5("LOCK %d %d was %d(%d)\n",
pFile->h, locktype, pFile->locktype, pFile->sharedLockByte);
/* If there is already a lock of this type or more restrictive on the
** OsFile, do nothing. Don't use the end_lock: exit path, as
** sqlite3OsEnterMutex() hasn't been called yet.
*/
//当前的锁>=locktype,则返回
if( pFile->locktype>=locktype ){
return SQLITE_OK;
}
/* Make sure the locking sequence is correct
*/
assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK );
assert( locktype!=PENDING_LOCK );
assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK );
/* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or
** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of
** the PENDING_LOCK byte is temporary.
*/
newLocktype = pFile->locktype;
/*两种情况: (1)如果当前文件处于无锁状态(获取读锁---读事务
**和写事务在最初阶段都要经历的阶段),
** (2)处于RESERVED_LOCK,且请求的锁为EXCLUSIVE_LOCK(写事务)
**则对执行加PENDING_LOCK
*/
/////////////////////(1)///////////////////
if( pFile->locktype==NO_LOCK
|| (locktype==EXCLUSIVE_LOCK && pFile->locktype==RESERVED_LOCK)
){
int cnt = 3;
//加pending锁
while( cnt-->0 && (res = LockFile(pFile->h, PENDING_BYTE, 0, 1, 0))==0 ){
/* Try 3 times to get the pending lock. The pending lock might be
** held by another reader process who will release it momentarily.
*/
TRACE2("could not get a PENDING lock. cnt=%d\n", cnt);
Sleep(1);
}
//设置为gotPendingLock为1,使和在后面要释放PENDING锁
gotPendingLock = res;
}
/* Acquire a shared lock
*/
/*获取shared lock
**此时,事务应该持有PENDING锁,而PENDING锁作为事务从UNLOCKED到
**SHARED_LOCKED 的一个过渡,所以事务由PENDING->SHARED
**此时,实际上锁处于两个状态:PENDING和SHARED,
** 直到后面释放PENDING锁后,才真正处于SHARED状态
*/
////////////////(2)/////////////////////////////////////
if( locktype==SHARED_LOCK && res ){
assert( pFile->locktype==NO_LOCK );
res = getReadLock(pFile);
if( res ){
newLocktype = SHARED_LOCK;
}
}
/* Acquire a RESERVED lock
*/
/*获取RESERVED
**此时事务持有SHARED_LOCK,变化过程为SHARED->RESERVED。
**RESERVED 锁的作用就是为了提高系统的并发性能
*/
////////////////////////(3)/////////////////////////////////
if( locktype==RESERVED_LOCK && res ){
assert( pFile->locktype==SHARED_LOCK );
//加RESERVED锁
res = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0);
if( res ){
newLocktype = RESERVED_LOCK;
}
}
/* Acquire a PENDING lock
*/
/*获取PENDING锁
**此时事务持有RESERVED_LOCK,且事务申请EXCLUSIVE_LOCK
**变化过程为:RESERVED->PENDING。
**PENDING状态只是唯一的作用就是防止写饿死.
**读事务不会执行该代码,但是写事务会执行该代码,
**执行该代码后gotPendingLock设为0,后面就不会释放PENDING锁。
*/
//////////////////////////////(4)////////////////////////////////
if( locktype==EXCLUSIVE_LOCK && res ){
//这里没有实际的加锁操作,只是把锁的状态改为PENDING状态
newLocktype = PENDING_LOCK;
//设置了gotPendingLock,后面就不会释放PENDING锁了,
//相当于加了PENDING锁,实际上是在开始处加的PENDING锁
gotPendingLock = 0;
}
/* Acquire an EXCLUSIVE lock
*/
/*获取EXCLUSIVE锁
**当一个事务执行该代码时,它应该满足以下条件:
**(1)锁的状态为:PENDING (2)是一个写事务
**变化过程:PENDING->EXCLUSIVE
*/
/////////////////////////(5)///////////////////////////////////////////
if( locktype==EXCLUSIVE_LOCK && res ){
assert( pFile->locktype>=SHARED_LOCK );
res = unlockReadLock(pFile);
TRACE2("unreadlock = %d\n", res);
res = LockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0);
if( res ){
newLocktype = EXCLUSIVE_LOCK;
}else{
TRACE2("error-code = %d\n", GetLastError());
}
}
/* If we are holding a PENDING lock that ought to be released, then
** release it now.
*/
/*此时事务在第2步中获得PENDING锁,它将申请SHARED_LOCK(第3步,和图形相对照),
**而在之前它已经获取了PENDING锁,
**所以在这里它需要释放PENDING锁,此时锁的变化为:PENDING->SHARED
*/
//////////////////////////(6)/////////////////////////////////////
if( gotPendingLock && locktype==SHARED_LOCK ){
UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0);
}
/* Update the state of the lock has held in the file descriptor then
** return the appropriate result code.
*/
if( res ){
rc = SQLITE_OK;
}else{
TRACE4("LOCK FAILED %d trying for %d but got %d\n", pFile->h,
locktype, newLocktype);
rc = SQLITE_BUSY;
}
//在这里设置文件锁的状态
pFile->locktype = newLocktype;
return rc;
}
语句序列:(1)——>(2)——>(6)
相
应的状态真正的变化过程为:UNLOCKED→PENDING(1)→PENDING、
SHARED(2)→
SHARED(6)→UNLOCKED
(II)对于一个写事务完整经过:
第一阶段:
语句序列:(1)——>(2)——>(6)
状态变
化:UNLOCKED→PENDING(1)→PEN
DING、SHARED(2)
→SHARED(6)。此时事务获得SHARED LOCK。
第二个阶段
:
语句序列:(3)
此时事务获得RESERVED LOCK。
第三个阶段:
事务执行修改操作。
第四个阶段:
语句序列:(1)——>(4)——>(5)
状态变化为:
RESERVED→
RESERVED 、PENDING(1)
→PENDING(4)→EXCLUSIVE(5)。
此时事务获得排斥锁,就可以进行写磁盘操作了。
SQLite的加锁机制会不会出现死锁?
这是一个很有意思的问题,对于任何采取加锁
作为并发控制机制的DBMS都得考虑这个问题。有两种方式处理死锁问题:(1)死锁预防(deadlock
prevention)(2)死锁检测(deadlock detection)与死锁恢复(deadlock
recovery)。SQLite采取了第一种方式,如果一个事务不能获取锁,它会重试有限次(这个重试次数可以由应用程序运行预先设置,默认为1次)
——这实际上是基本锁超时的机制。如果还是不能获取锁,SQLite返回SQLITE_BUSY错误给应用程序,应用程序此时应该中断,之后再重试;或者
中止当前事务。虽然基于锁超时的机制简单,容易实现,但是它的缺点也是明显的——资源浪费。
既然SQLite采取了这种机制,所以应用程序得处理
SQLITE_BUSY 错误,先来看一个会产生SQLITE_BUSY错误的例子:
答案就是为你的
程序选择正确合适的事务类型。
SQLite有三种不同的事务类型,这不同于锁的状态。事务可以从DEFERRED,IMMEDIATE或者
EXCLUSIVE,一个事务的类型在BEGIN命令中指定:
BEGIN [ DEFERRED | IMMEDIATE |
EXCLUSIVE ] TRANSACTION;
一个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开始你的事务。
发表评论
-
(转)Andriod是什么
2010-12-02 11:24 1613导读:Sans Serif是Google的 ... -
(转)SQLite入门与分析(六)---再谈SQLite的锁
2010-11-19 00:09 941写在前 面:SQLite封锁机制的实现需要底层文件系统的 ... -
(转)SQLite入门与分析(四)---Page Cache之事务处理(3)
2010-11-19 00:01 950Code 写在前面:由于 内容较多,所以断续没有写完的 ... -
(转)SQLite入门与分析(四)---Page Cache之事务处理(2)
2010-11-18 23:57 1140写在前面:个人认为pager层是SQLite实现最为核心的 ... -
(转)SQLite入门与分析(四)---Page Cache之事务处理(1)
2010-11-18 23:53 939写在前面:从本章开始,将对SQLite的每个模块进行讨论。 ... -
(转)SQLite入门与分析(三)---内核概述(2)
2010-11-18 23:48 1300写在前面:本节 是前 ... -
(转)SQLite入门与分析(三)---内核概述(1)
2010-11-18 23:41 795写在前面:从本 章开始, ... -
(转)SQLite入门与分析(二)---设计与概念(续)
2010-11-18 23:38 1039写在前面:本节 讨论事务,事务是DBMS最核心的技术之一 ... -
(转)SQLite入门与分析(二)---设计与概念
2010-11-18 23:35 789写在前面:谢谢各位的 ... -
(转)SQLite入门与分析(一)
2010-11-18 23:31 904写在前面:出于项目的 需要,最近打算对SQLite的内核 ... -
(转)深入研究B树索引(五)续
2010-11-18 15:10 9255.3 重建 B 树索引 ... -
(转)深入研究B树索引(五)
2010-11-18 15:07 11905. 重建 B ... -
(转)深入研究B树索引(四)续
2010-11-18 14:58 9114.2 B 树索引的对于删除( DEL ... -
(转)深入研究B树索引(三、四)
2010-11-18 14:44 7113. B 树索 ... -
(转)深入研究B树索引(二)
2010-11-18 14:20 7562. B 树索引的内部结构 ... -
(转)深入研究B树索引(一)
2010-11-18 14:12 1000摘要: 本文对B 树索引的结构、内部管理等方面做了一个全面 ... -
(转)B树、B-树、B+树、B*树都是什么
2010-11-17 23:46 671B 树 即二叉搜 ... -
画UML图时注意的几个原则(转)
2010-08-03 12:34 1622软件开发中,分析和设计时,文档的编写和思想的交流,经常要绘制各 ... -
你是个软件架构师吗?(转)
2010-07-14 11:11 661开发和架构的界限难以 ... -
(转)同曲异奏——高效能项目团队的五大特点
2010-03-29 00:24 856同曲异奏——高效能项 ...
相关推荐
SQLite入门与分析(四)---Page Cache之事务处理.doc SQLite入门与分析(五)---Page Cache之并发控制.doc SQLite入门与分析(六)---再谈SQLite的锁.doc
二、Android下的SQLite编译与基础入门 在Android开发中,SQLite通过Android SDK的SQLiteOpenHelper类进行操作。1,首先,我们需要创建一个继承自SQLiteOpenHelper的子类,定义数据库版本号和升级方法。2,然后,我们...
2. **页(Page)和缓存(Cache)**:SQLite将数据存储在页上,每个页有固定的大小。缓存机制用于提高数据访问速度。 3. **触发器(Trigger)**:允许在特定事件(如INSERT、UPDATE或DELETE)发生时自动执行指定的SQL...
1. 增大Page Cache:SQLite使用内存中的Page Cache存储数据页,增大缓存可以减少磁盘I/O。通过PRAGMA cache_size可以设置缓存的页面数量,例如`PRAGMA cache_size = 2000;`表示设置为2000个页面,每个页面通常是4KB...
通过深入研究SQLite源码,开发者可以学习到数据库设计、SQL解析、存储引擎、并发控制等核心概念,这将对提升数据库开发和优化能力大有裨益。同时,源码阅读也是学习软件工程实践,如错误处理、测试和调试的好途径。
总结来说,SQLite3源码涵盖了数据库系统设计的多个核心领域,包括SQL解析与编译、虚拟机执行、数据存储与缓存、事务管理、并发控制以及错误处理。对SQLite3源码的深入学习不仅可以帮助开发者理解数据库的工作原理,...
增大 cache_size 或者 page_size 设置,可以增加SQLite在内存中存储的数据量,减少磁盘I/O。 9. **硬件优化**:升级硬件,比如使用更快的SSD硬盘,或者增加内存,都能显著提升SQLite的性能。 10. **升级SQLite版本...
5. **页面缓存(Page Cache)**:为了提高性能,SQLite维护了一个内存中的数据库页面缓存。当数据需要读取或写入时,会先操作缓存,减少对磁盘的访问。 6. **索引(Indexing)**:SQLite支持多种类型的索引,如唯一...
SQLite PRAGMA 是 SQLite 数据库系统中的一种特殊命令,它用于查询和设置 SQLite 环境中的各种配置选项和状态标志。PRAGMA 命令既能够读取当前的配置值,也能根据需要设置新的值。这使得开发人员可以根据具体的需求...
SQLite具备事务处理功能,其生命周期从开始到结束,包括事务对其他连接的影响,对并发性的控制至关重要。事务是数据库管理中一个非常重要的概念,它能够保证数据的一致性和完整性。 SQLite的零配置运行模式意味着它...
开发阶段通常使用SQLite数据库,但在生产环境中,为了支持更大的并发和数据量,项目切换到了MySQL。Django的数据库配置允许灵活地切换不同数据库。例如,通过定义一个字典结构,包含不同数据库的配置,然后根据环境...