SQLite入门与分析(五)---Page Cache之并发控制
写在前面:本节主要谈谈SQLite的锁机制,SQLite是基于锁来实现并发控制的,所以本节的内容实际上是属于事务处理的,但是SQLite的锁机制实现非常的简单而巧妙,所以在这里单独讨论一下。如果真正理解了它,对整个事务的实现也就理解了。而要真正理解SQLite的锁机制,最好方法就是阅读SQLite的源码,所以在阅读本文时,最好能结合源码。SQLite的锁机制很巧妙,尽管在本节中的源码中,我写了很多注释,也是我个人在研究时的一点心得,但是我发现仅仅用言语,似乎不能把问题说清楚,只有通过体会,才能真正理解SQLite的锁机制。好了,下面进入正题。
SQLite的并发控制机制是采用加锁的方式,实现非常简单,但也非常的巧妙,本节将对其进行一个详细的解剖。请仔细阅读下图,它可以帮助更好的理解下面的内容。
1、RESERVED LOCK SQLite在pager层获取锁的函数如下:
//获取一个文件的锁,如果忙则重复该操作, 直到busy回调用函数返回flase,或者成功获得锁 staticintpager_wait_on_lock(Pager*pPager,intlocktype){ intrc; 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; } } returnrc; } Windows下具体的实现如下: intwinLock(OsFile*id,255)">intrc=SQLITE_OK;/*Returncodefromsubroutines*/ intres=1;ResultofawindowslockcallintnewLocktype;Setid->locktypetothisvaluebeforeexitingintgotPendingLock=0;TrueifweacquiredaPENDINGlockthistime*/ winFile*pFile=(winFile*)id; assert(pFile!=0); TRACE5("LOCK%d%dwas%d(%d)n", pFile->h,locktype,pFile->locktype,pFile->sharedLockByte); Ifthereisalreadyalockofthistypeormorerestrictiveonthe **OsFile,donothing.Don'tusetheend_lock:exitpath,as **sqlite3OsEnterMutex()hasn'tbeencalledyet. 当前的锁>=locktype,则返回if(pFile->locktype>=locktype){ returnSQLITE_OK; } Makesurethelockingsequenceiscorrect */ assert(pFile->locktype!=NO_LOCK||locktype==SHARED_LOCK); assert(locktype!=PENDING_LOCK); assert(locktype!=RESERVED_LOCK||pFile->locktype==SHARED_LOCK); LockthePENDING_LOCKbyteifweneedtoacquireaPENDINGlockor **aSHAREDlock.IfweareacquiringaSHAREDlock,theacquisitionof **thePENDING_LOCKbyteistemporary. */ newLocktype=pFile->locktype; 两种情况:(1)如果当前文件处于无锁状态(获取读锁---读事务 **和写事务在最初阶段都要经历的阶段), **(2)处于RESERVED_LOCK,且请求的锁为EXCLUSIVE_LOCK(写事务) **则对执行加PENDING_LOCK */ /////////////////////(1)/////////////////// if(pFile->locktype==NO_LOCK ||(locktype==EXCLUSIVE_LOCK&&pFile->locktype==RESERVED_LOCK) ){ intcnt=3; 加pending锁while(cnt-->0&&(res=LockFile(pFile->h,PENDING_BYTE,0,128)">1,128)">0))==0){ Try3timestogetthependinglock.Thependinglockmightbe **heldbyanotherreaderprocesswhowillreleaseitmomentarily. */ TRACE2(couldnotgetaPENDINGlock.cnt=%dn Sleep(1); } 设置为gotPendingLock为1,使和在后面要释放PENDING锁 gotPendingLock=res; } Acquireasharedlock 获取sharedlock **此时,事务应该持有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; } } AcquireaRESERVEDlock 获取RESERVED **此时事务持有SHARED_LOCK,变化过程为SHARED->RESERVED。 **RESERVED锁的作用就是为了提高系统的并发性能 ////////////////////////(3)///////////////////////////////// if(locktype==RESERVED_LOCK&&res){ assert(pFile->locktype==SHARED_LOCK); 加RESERVED锁 res=LockFile(pFile->h,RESERVED_BYTE,128)">0); if(res){ newLocktype=RESERVED_LOCK; } } AcquireaPENDINGlock 获取PENDING锁 **此时事务持有RESERVED_LOCK,且事务申请EXCLUSIVE_LOCK **变化过程为:RESERVED->PENDING。 **PENDING状态只是唯一的作用就是防止写饿死. **读事务不会执行该代码,但是写事务会执行该代码, **执行该代码后gotPendingLock设为0,后面就不会释放PENDING锁。 //////////////////////////////(4)//if(locktype==EXCLUSIVE_LOCK&&res){ 这里没有实际的加锁操作,只是把锁的状态改为PENDING状态 newLocktype=PENDING_LOCK; 设置了gotPendingLock,后面就不会释放PENDING锁了,0)">相当于加了PENDING锁,实际上是在开始处加的PENDING锁 gotPendingLock=0; } AcquireanEXCLUSIVElock 获取EXCLUSIVE锁 **当一个事务执行该代码时,它应该满足以下条件: **(1)锁的状态为:PENDING(2)是一个写事务 **变化过程:PENDING->EXCLUSIVE /(5)//////////////////////////////////////////if(locktype==EXCLUSIVE_LOCK&&res){ assert(pFile->locktype>=SHARED_LOCK); res=unlockReadLock(pFile); TRACE2(unreadlock=%dn res=LockFile(pFile->h,SHARED_FIRST,SHARED_SIZE,255)">if(res){ newLocktype=EXCLUSIVE_LOCK; }else{ TRACE2(error-code=%dn } } IfweareholdingaPENDINGlockthatoughttobereleased,then **releaseitnow. 此时事务在第2步中获得PENDING锁,它将申请SHARED_LOCK(第3步,和图形相对照), **而在之前它已经获取了PENDING锁, **所以在这里它需要释放PENDING锁,此时锁的变化为:PENDING->SHARED //(6)if(gotPendingLock&&locktype==SHARED_LOCK){ UnlockFile(pFile->h,128)">0); } Updatethestateofthelockhasheldinthefiledescriptorthen **returntheappropriateresultcode. if(res){ rc=SQLITE_OK; }else{ TRACE4(LOCKFAILED%dtryingfor%dbutgot%dn->h, locktype,newLocktype); rc=SQLITE_BUSY; } 在这里设置文件锁的状态 pFile->locktype=newLocktype; returnrc; } 在几个关键的部位标记数字。 (I)对于一个读事务会的完整经过: 注:在上面的过程中,由于(1)的执行,使得某些时刻SQLite处于两种状态,但它持续的时间很短,从某种程度上来说可以忽略,但是为了把问题说清楚,在这里描述了这一微妙而巧妙的过程。 4、SQLite的死锁问题 5、事务类型(Transaction Types) 所以应用程序应该尽量避免产生死锁,那么应用程序如何做可以避免死锁的产生呢? (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |