星空网 > 软件开发 > 数据库

SQLite学习笔记(六)共享缓存

介绍

       通常情况下,sqlite中每个连接都会一个独立的pager对象,pager对象中管理了该连接的缓存信息,通过pragma cache_size指令可以设置缓存大小,默认是2000个page,每个page是1024B。这样导致了对于同一个数据文件,多个连接各自维护了自己的一份缓存,在高并**况下,可能导致使用大量的内存。而sqlite作为一个嵌入式数据库,通常用于嵌入式设备,内存可能比较有限,为了应对这种问题,sqlite提供了一种方法,通过让多个连接公用一个pager对象,共享同一份缓存。

      当打开这个特性后,多个连接可以共享一个pager对象,这样在一定程度上减少了内存的消耗和文件的IO次数。一个线程的多个连接对象,以及多个线程的多个连接对象都可以采用这种方式来共享缓存。由于sqlite以动态库方式嵌入在应用程序中,每个应用程序有自己独立的进程空间,因此该特性不能用于进程之间,同一个进程可以共享一份缓存,进程之间各自维护自己的缓存。关于缓存的实现后面会单独写一篇文章。下图展示了启用共享缓存后的结构图。

SQLite学习笔记(六)共享缓存
图1

从图1中可以看到,Process1中的两个连接共享BtShared对象,BtShared对象对应一个Pager对象,而缓存由Pager对象管理,因此整个Process1中的所有连接公用一个缓存,通过Pager模块操作Page,IO操作对上层模块透明。

实现原理

      调用接口sqlite3_open_v2打开连接时,通过指定参数SQLITE_OPEN_SHAREDCACHE来声明连接采用共享缓存模式。之前SQLite系列(二):常规性能测试 中的单表主键查询测试章节中提到,开启共享缓存模式下,导致应用的程序的并发性能大大下降,多线程情况下,CPU也只能用一个核。因此大家在实际使用中,要权衡内存和并行度,来确定是否开启共享缓存模式。
      我们的测试场景都是只读,理论上不应该存在并发冲突,那么为什么不能并行?这里要看共享缓存的实现了。从图1中也可以看到,每个连接有一个btree对象,多个btree对象通过共享BtShared对象来共享缓存,BtShared对象通过mutux来维护里面的成员,包括page cache的管理,table-lock信息等,因此这个mutex是一个热点,在高并发场景下,多个线程同时访问BtShared对象,会由于竞争mutex,无法充分并发,导致并行度差。

table-lock

      默认情况下,sqlite只通过文件锁就可以实现读写互斥,读读并发的效果,关于这点我在SQLite系列(五):SQLITE封锁机制中已经说明。那么为什么要引入table-lock?这个也是拜共享缓存所赐。共享缓存模式下,多个btree对象对应同一个BtShared对象,在执行更新时,首先会修改pager中cache,此时更新事务加了reserved-lock,与读事务的shared-lock不互斥。为了避免读到脏页,在共享缓存模式下,增加了table-lock,避免读写事务同时访问同一个cache,导致脏读的情况发生。用户访问具体某个表的page之前,会首先调用sqlite3BtreeLockTable对该表设置一个READ-LOCK(select 操作)或WRITE-LOCK(DML操作),如果发现冲突,则抛出SQLITE_LOCKED错误。通过这种方式,保证了多个线程不会同时对一个表进行读写操作。函数sqlite3BtreeLockTable部分实现如下:

sqlite3BtreeEnter(p);//判断加锁是否冲突rc = querySharedCacheTableLock(p, iTab, lockType);if( rc==SQLITE_OK ){ //加锁 rc = setSharedCacheTableLock(p, iTab, lockType);} sqlite3BtreeLeave(p); 

从上面的代码可以看到,若加锁冲突,则直接报SQLITE_LOCKED错误;否则加上锁。注意看到判断加锁和加锁过程通过BtShared对象的mutex来保护(sqlite3BtreeEnter,sqlite3BtreeLeave),因此加锁过程是串行的,table-lock链表不会被多个线程同时操作。

querySharedCacheTableLock代码逻辑

/* If some other connection is holding an exclusive lock, the** requested lock may not be obtained.*/if( pBt->pWriter!=p && (pBt->btsFlags & BTS_EXCLUSIVE)!=0 ){    sqlite3ConnectionBlocked(p->db, pBt->pWriter->db);   return SQLITE_LOCKED_SHAREDCACHE;}

setSharedCacheTableLock逻辑
for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){  if( pIter->iTable==iTable && pIter->pBtree==p ){    pLock = pIter;  break; }}/*create a table lock*/if( !pLock ){  pLock = (BtLock *)sqlite3MallocZero(sizeof(BtLock));  if( !pLock ){    return SQLITE_NOMEM; } pLock->iTable = iTable;pLock->pBtree = p;pLock->pNext = pBt->pLock;pBt->pLock = pLock;}

遍历BtShared对象中已有锁链表,比较iTable和对应的pBtree是否与自身相同(自己是否已经加过),若没有,则申请锁对象,加入链表。

加锁流程

我们知道sqlite有两种日志模式,默认的DELETE模式和WAL模式,下面我会介绍开启共享缓存模式后,更新操作的加锁流程,主要变化在于table-lock。

普通日志模式+共享缓存模式

  1. 开启事务:shared-lock[sqlite3BtreeBeginTrans]
  2. DML操作:
  • 文件锁,reserved-lock
  • table-lock,
    将对应的表加锁,同一个表的读锁与写锁互斥
  • 读取表对应的page
  1. 提交:
  • 加execlusive-lock [sqlite3PagerCommitPhaseOne,刷日志]
  • 删除日志文件
  • 释放execlusive-lock
  • 释放table-lock

 WAL日志模式+共享缓存模式

  1. 开启事务,shared-lock[sqlite3BtreeBeginTrans]
  2. DML操作
  • 数据文件锁,shared-lock
  • wal日志文件write-lock
  • table-lock
  1. 提交
  • 释放table-lock
  • 释放日志文件write-lock
  • 释放数据文件shared-lock



原标题:SQLite学习笔记(六)共享缓存

关键词:sql

sql
*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。

亚马逊卖家必读:11月亚马逊新政:https://www.kjdsnews.com/a/664455.html
关于今年亚马逊日本的核心优惠,你想知道的都在这篇了!:https://www.kjdsnews.com/a/664456.html
22年坚守:阿里巴巴国际站重新定义跨境贸易平台:https://www.kjdsnews.com/a/664457.html
今年双十一,淘宝京东亲自杀入东南亚:https://www.kjdsnews.com/a/664458.html
沙特独立站卖家请注意!商务部再次重申此规定:https://www.kjdsnews.com/a/664459.html
Shopee多店铺死号,深度解析店铺防关联:https://www.kjdsnews.com/a/664460.html
OpenAI王炸模型Sora引爆科技圈,我们第一时间深读了官方技术报告:https://www.kjdsnews.com/a/1836569.html
除了HARO,你还应该知道这些媒体平台:https://www.kjdsnews.com/a/1836570.html
相关文章
我的浏览记录
最新相关资讯
海外公司注册 | 跨境电商服务平台 | 深圳旅行社 | 东南亚物流