if( pPage && (pPage->pLruNext || pPage==pcache1.pLruTail) ){ /* 从双向链表中删除 */ if( pPage->pLruPrev ){
pPage->pLruPrev->pLruNext = pPage->pLruNext; }
if( pPage->pLruNext ){
pPage->pLruNext->pLruPrev = pPage->pLruPrev; }
/* 如果是头结点,改头指针 */ if( pcache1.pLruHead==pPage ){
pcache1.pLruHead = pPage->pLruNext; }
/* 如果是尾结点,改尾指针 */ if( pcache1.pLruTail==pPage ){
pcache1.pLruTail = pPage->pLruPrev; }
pPage->pLruNext = 0; pPage->pLruPrev = 0;
pPage->pCache->nRecyclable--; /* 修改可重用页数 */ } }
使一个页不被钉住的程序如下,比典型的双向链表的插入算法多一点儿操作: /*
实现sqlite3_pcache.xUnpin方法。 使页不被钉住(使其可被回收) */
static void pcache1Unpin(sqlite3_pcache *p, void *pPg, int reuseUnlikely){ PCache1 *pCache = (PCache1 *)p;
PgHdr1 *pPage = PAGE_TO_PGHDR1(pCache, pPg);
if( reuseUnlikely || pcache1.nCurrentPage>pcache1.nMaxPage ){
/*如果该页看起来不会再被使用了,且LRU中页数已经太多,直接释放该页。*/ pcache1RemoveFromHash(pPage); /* 从该页所在的页缓冲区中删除 */ pcache1FreePage(pPage); /* 释放该页 */ /* 当然就不需要再向LRU链表中插入了 */ }else{
/* 将该页插入到LRU链表的头部。 */
if( pcache1.pLruHead ){ /* LRU链表不为空 */ pcache1.pLruHead->pLruPrev = pPage; pPage->pLruNext = pcache1.pLruHead; pcache1.pLruHead = pPage; }else{ /* LRU链表为空 */ pcache1.pLruTail = pPage;
pcache1.pLruHead = pPage; }
pCache->nRecyclable++; } }
写到这里还有两个问题我也没搞清楚: 一是“净化”机制没搞清楚;
二是我在跟踪SQLite运行时,静态缓冲区的大小总是为0,而不是传说中的“默认为2000页”。
没搞清楚就算了,我不怕丢人。
2.4. sqlite3_stmt主线
现在开始讨论SQLite内存数据结构的第2条主线。
2.4.1. Vdbe结构
记得网上有人问过,在SQLite源程序中为什么找不到sqlite3_stmt结构的定义。在SQLite对外的接口中都是使用sqlite3_stmt,而其实在SQLite的实现中,\是一个指向VDBE结构的不透明的指针。 /*
虚拟机的一个实例。
此结构包含了虚拟机的全部状态。 */
struct Vdbe {
Vdbe *pPrev,*pNext; /* 数据库连接中虚拟机链表的指针域 */ int nOp; /* 程序中的指令数 */
int nOpAlloc; /* 为aOp[]分配的槽位数 */ Op *aOp; /* 存放虚拟机程序的空间 */ Mem *aColName; /* 返回的各字段的名称 */ Mem *pResultSet; /* 指向一个结果数组的指针 */ u16 nResColumn; /* 结果集中每行的字段数 */ u16 nCursor; /* apCsr[]中的元素数 */
VdbeCursor **apCsr; /* 此数组中的每个元素为一个打开的游标 */ int pc; /* 程序计数器 */ };
每个数据库连接中可能有多个活动的虚拟机,这些虚拟机被组织成一个双向链表,pPrev和pNext域分别指向链表中的前趋和后继。
在sqlite3结构中有一个域pVdbe,为指向此双向链表的头指针。新创建的虚拟机插在该双向链表的头部,创建虚拟机的程序如下: /*
创建一个新的虚拟机。 */
Vdbe *sqlite3VdbeCreate(sqlite3 *db){ Vdbe *p;
p = sqlite3DbMallocZero(db, sizeof(Vdbe) ); if( p==0 ) return 0; p->db = db;
if( db->pVdbe ){
db->pVdbe->pPrev = p; }
p->pNext = db->pVdbe; p->pPrev = 0; db->pVdbe = p; return p; }
2.4.2. Mem结构
/*
Mem结构
在SQLite内部,vdbe用Mem结构来操作几乎所有的SQL值。 Mem结构会给出同一个值的多种表示法(string、integer等)。 一个值(也就是Mem结构)具有以下属性:
每个值具有一个弱类型。存储在Mem结构中的值的弱类型由MemType(Mem*)返回。
类型可以是SQLITE_NULL、SQLITE_INTEGER、SQLITE_REAL、SQLITE_TEXT或SQLITE_BLOB。 */
struct Mem { union {
i64 i; /* 整数值 */
int nZero; /* 当MEM_Zero位设置时有效 */
FuncDef *pDef; /* Used only when flags==MEM_Agg */ RowSet *pRowSet; /* Used only when flags==MEM_RowSet */ VdbeFrame *pFrame; /* Used when flags==MEM_Frame */ } u;
double r; /* 实数值 */
sqlite3 *db; /* 相关的数据库连接 */ char *z; /* 字符串或BLOB值 */
int n; /* 字符串值中的字节数,不包括'\\0' */ u16 flags; /* 由MEM_Null, MEM_Str, MEM_Dyn等标志位组成,详见后面的注释 */ u8 type; /* SQLITE_NULL, SQLITE_TEXT, SQLITE_INTEGER等值中的一个 */ u8 enc; /* 编码:SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */ };
2.4.3. VdbeCursor结构
游标在虚拟机一层的表现形式。 /*
游标是数据库中特定的BTree指针。
游标可以用特定的键值在BTree中搜寻入口,或循环处理所有的入口。
你可以向BTree中插入新的入口或从游标当前指向的入口中取出键值或数据值。 虚拟机打开的每个游标为此结构的一个实例。 */
struct VdbeCursor {
BtCursor *pCursor; /* 后台游标结构 */
Btree *pBt; /* Separate file holding temporary table */ int nField; /* 字段数量 */ /* 游标当前指向的数据记录的信息。
仅在cacheStatus与Vdbe.cacheCtr匹配时有效。 */
u32 cacheStatus; /* 如果此值与Vdbe.cacheCtr匹配,说明缓冲区可用 */ int payloadSize; /* Payload大小,以字节为单位。 */ u32 *aType; /* 各字段的数据类型值 */ u32 *aOffset; /* 各字段数据的偏移量 */
u8 *aRow; /* 当前行的数据(如果它们都在同一个页中) */ };
注:各域的含义解释起来挺麻烦的,就不解释了。看看参考文献二,就全明白了。下面几个结构也存在此情况,不再说明。
2.4.4. BtCursor记录
/*
游标是指向一个特定入口的指针,这个入口在一个数据库文件的特定b-tree中。 入口由MemPage和MemPage.aCell[]的下标确定。
一个数据库文件可被多个数据库连接共享,但游标不能被共享。 */
struct BtCursor {
Pgno pgnoRoot; /* 此Btree的根页页号 */
CellInfo info; /* 当前指向的单元(cell)的解析结果 */ void *pKey; /* 游标最后已知的位置的键值 */ i64 nKey; /* pKey的大小或最后的整数键值 */
i16 iPage; /* 当前页在apPage中的索引 */ MemPage *apPage[BTCURSOR_MAX_DEPTH]; /* 从根页到本页的所有页 */
u16 aiIdx[BTCURSOR_MAX_DEPTH]; /* apPage[i]中的当前索引。空注:单元 指针数组中的当前下标。 */ };
相关推荐: