sqlite btree打开
之前在openDatabase函数中,看了大致流程,里面有个很重要的函数sqlite3BtreeOpen
看一下这个函数的流程吧
//打开数据库文件,实际上,btree是将数据库文件加载为一个有序的key/value形式的tree
//zFilename,数据库文件名,如果是NULL,会创建一个临时的数据库,这个数据库
//只存在内存当中,或者在磁盘上的内存缓存中,调用sqlite3BtreeClose关闭时,临时数据库会被删除
//数据库文件名格式,如果是“:memory”,会创建一个内存数据库,关闭时自动删除
//flags,按位掩码,每一位表示一个值,可能会有BTREE_OMIT_JOURNAL或BTREE_MEMORY
//如果数据库使用功效内存模式打开,那么不允许再打开,因为共享btree会导致部分锁异常
int sqlite3BtreeOpen(
//vfs,虚拟文件系统,sqlite的最底层,实现跨平台的系统访问
sqlite3_vfs *pVfs, /* VFS to use for this b-tree */
const char *zFilename, /* Name of the file containing the BTree database */
sqlite3 *db, /* Associated database handle */
Btree **ppBtree, /* Pointer to new Btree object written here */
int flags, /* Options */
int vfsFlags /* Flags passed through to sqlite3_vfs.xOpen() */
){
//btree的共享部分
BtShared *pBt = 0; /* Shared part of btree structure */
Btree *p; /* Handle to return */
sqlite3_mutex *mutexOpen = 0; /* Prevents a race condition. Ticket #3537 */
int rc = SQLITE_OK; /* Result code from this function */
u8 nReserve; /* Byte of unused space on each page */
unsigned char zDbHeader[100]; /* Database header content */
/* True if opening an ephemeral, temporary database */
//判断是否打开临时数据库
const int isTempDb = zFilename==0 || zFilename[0]==0;
/* Set the variable isMemdb to true for an in-memory database, or
** false for a file-based database.
*/
//SQLITE_OMIT_MEMORYDB, 禁止内存数据库
#ifdef SQLITE_OMIT_MEMORYDB
const int isMemdb = 0;
#else
//判断是否为内存数据库,根据函数注释和下面的代码,明显的,有3种情况:
//1. szFilename以 ":memory:"开头,且不等于空
//2. szFilename为NULL,并且判断db为内存数据库
//3. vfsFlags中设置了SQLITE_OPEN_MEMORY
const int isMemdb = (zFilename && strcmp(zFilename, ":memory:")==0)
|| (isTempDb && sqlite3TempInMemory(db))
|| (vfsFlags & SQLITE_OPEN_MEMORY)!=0;
#endif
//断言db,pVfs不能为空
assert( db!=0 );
assert( pVfs!=0 );
assert( sqlite3_mutex_held(db->mutex) );
//断言,flags只支持<=0x80的几个值
assert( (flags&0xff)==flags ); /* flags fit in 8 bits */
/* Only a BTREE_SINGLE database can be BTREE_UNORDERED */
//BTREE_SINGLE,数据库最多有1个btree
//BTREE_UNORDERED,使用hash表,tree无序
assert( (flags & BTREE_UNORDERED)==0 || (flags & BTREE_SINGLE)!=0 );
/* A BTREE_SINGLE database is always a temporary and/or ephemeral */
//通常只有1个btree的数据库,都是临时或生命周期很短的数据库
assert( (flags & BTREE_SINGLE)==0 || isTempDb );
if( isMemdb ){
//BTREE_MEMORY,内存数据库
flags |= BTREE_MEMORY;
}
//如果打开主数据库,并且是临时数据库,设置临时数据库打开标志
if( (vfsFlags & SQLITE_OPEN_MAIN_DB)!=0 && (isMemdb || isTempDb) ){
vfsFlags = (vfsFlags & ~SQLITE_OPEN_MAIN_DB) | SQLITE_OPEN_TEMP_DB;
}
//申请内存,初始化btree,默认值为0
p = sqlite3MallocZero(sizeof(Btree));
if( !p ){
return SQLITE_NOMEM_BKPT;
}
//事务类型,有3中,TRANS_NONE,无事务,TRANS_READ,仅激活读事务
//TRANS_WRITE,激活写事务
//多个连接共享Btree的数据时,仅有一个是写,其他都是读
p->inTrans = TRANS_NONE;
p->db = db;
//SQLITE_OMIT_SHARED_CACHE,禁止共享内存模式,设置后会明显的提高表现性能
#ifndef SQLITE_OMIT_SHARED_CACHE
//允许共享内存
//设置p指向的tree获得锁
p->lock.pBtree = p;
//iTable,表的根页面,每个表或者索引,都是一个tree
//tree的第一个页面,是根页面,根页面的前面一些字节存储了一些参数信息
p->lock.iTable = 1;
#endif
#if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO)
//允许共享内存,允许写入磁盘
//SQLITE_OMIT_DISKIO,禁止数据库写到磁盘当中,强制数据库只存在内存当中
//官方文档说这个选项已经不维护了,在新版本当中可能失效
/*
** If this Btree is a candidate for shared cache, try to find an
** existing BtShared object that we can share with
*/
//SQLITE_OPEN_URI,允许URI文件名解析,此时文件名参数以"file:"开头,默认是不允许URI解析的
//临时数据库,内存数据库
if( isTempDb==0 && (isMemdb==0 || (vfsFlags&SQLITE_OPEN_URI)!=0) ){
//SQLITE_OPEN_SHAREDCACHE,允许数据库连接使用共享内存模式
if( vfsFlags & SQLITE_OPEN_SHAREDCACHE ){
//获取文件名长度,最大为1G
int nFilename = sqlite3Strlen30(zFilename)+1;
//路径的最大长度,定义位于sqlite.h.in中struct sqlite3_vfs
int nFullPathname = pVfs->mxPathname+1;
//生成全路径名称变量
char *zFullPathname = sqlite3Malloc(MAX(nFullPathname,nFilename));
MUTEX_LOGIC( sqlite3_mutex *mutexShared; )
//多个数据库共享pBt,pBt表示每个btree的共享内容
p->sharable = 1;
//生成失败,返回内存错误
if( !zFullPathname ){
sqlite3_free(p);
return SQLITE_NOMEM_BKPT;
}
if( isMemdb ){
//如果是内存数据库,复制文件名
memcpy(zFullPathname, zFilename, nFilename);
}else{
//不是内存数据库,URI模式的
//此函数是与平台有关的,函数注册见相关平台的实现文件
//以win平台为例,可以查看os_win.c中的sqlite3_os_init
rc = sqlite3OsFullPathname(pVfs, zFilename,
nFullPathname, zFullPathname);
if( rc ){
sqlite3_free(zFullPathname);
sqlite3_free(p);
return rc;
}
}
//线程安全,获取锁
#if SQLITE_THREADSAFE
mutexOpen = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_OPEN);
sqlite3_mutex_enter(mutexOpen);
mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
sqlite3_mutex_enter(mutexShared);
#endif
//GLOBAL,一般情况下,#define GLOBAL(t,v) v
//sqlite3SharedCacheList,初始时为空,下面的循环不会执行
for(pBt=GLOBAL(BtShared*,sqlite3SharedCacheList); pBt; pBt=pBt->pNext){
assert( pBt->nRef>0 );
if( 0==strcmp(zFullPathname, sqlite3PagerFilename(pBt->pPager, 0))
&& sqlite3PagerVfs(pBt->pPager)==pVfs ){
int iDb;
for(iDb=db->nDb-1; iDb>=0; iDb--){
Btree *pExisting = db->aDb[iDb].pBt;
if( pExisting && pExisting->pBt==pBt ){
sqlite3_mutex_leave(mutexShared);
sqlite3_mutex_leave(mutexOpen);
sqlite3_free(zFullPathname);
sqlite3_free(p);
return SQLITE_CONSTRAINT;
}
}
p->pBt = pBt;
pBt->nRef++;
break;
}
}
sqlite3_mutex_leave(mutexShared);
sqlite3_free(zFullPathname);
}
#ifdef SQLITE_DEBUG
else{
/* In debug mode, we mark all persistent databases as sharable
** even when they are not. This exercises the locking code and
** gives more opportunity for asserts(sqlite3_mutex_held())
** statements to find locking problems.
*/
p->sharable = 1;
}
#endif
}
#endif
if( pBt==0 ){
/*
** The following asserts make sure that structures used by the btree are
** the right size. This is to guard against size changes that result
** when compiling on a different architecture.
*/
//变量类型大小校验
assert( sizeof(i64)==8 );
assert( sizeof(u64)==8 );
assert( sizeof(u32)==4 );
assert( sizeof(u16)==2 );
assert( sizeof(Pgno)==4 );
//申请内存,初始化为0,检测是否成功
pBt = sqlite3MallocZero( sizeof(*pBt) );
if( pBt==0 ){
rc = SQLITE_NOMEM_BKPT;
goto btree_open_out;
}
//生成新的pager对象
rc = sqlite3PagerOpen(pVfs, &pBt->pPager, zFilename,
sizeof(MemPage), flags, vfsFlags, pageReinit);
if( rc==SQLITE_OK ){
//修改内存map的最大大小
sqlite3PagerSetMmapLimit(pBt->pPager, db->szMmap);
//读取文件最开始的部分内容,长度为sizeof(zDbHeader),100个字节
rc = sqlite3PagerReadFileheader(pBt->pPager,sizeof(zDbHeader),zDbHeader);
}
if( rc!=SQLITE_OK ){
goto btree_open_out;
}
pBt->openFlags = (u8)flags;
pBt->db = db;
//设置busy handle
//当尝试将保留锁变成独占锁,或者没有锁改为共享锁,如果sqlite3OsLock返回
//SQLITE_BUSY的时候,会调用busy handle
sqlite3PagerSetBusyHandler(pBt->pPager, btreeInvokeBusyHandler, pBt);
p->pBt = pBt;
//pCursor指向所有打开的游标
//pPage1指向数据库的根页,也就是第一个页面
pBt->pCursor = 0;
pBt->pPage1 = 0;
//判断是否只读
if( sqlite3PagerIsreadonly(pBt->pPager) ) pBt->btsFlags |= BTS_READ_ONLY;
#if defined(SQLITE_SECURE_DELETE)
pBt->btsFlags |= BTS_SECURE_DELETE;
#elif defined(SQLITE_FAST_SECURE_DELETE)
pBt->btsFlags |= BTS_OVERWRITE;
#endif
/* EVIDENCE-OF: R-51873-39618 The page size for a database file is
** determined by the 2-byte integer located at an offset of 16 bytes from
** the beginning of the database file. */
//获取页大小,位于根页0x10,0x11位置
pBt->pageSize = (zDbHeader[16]<<8) | (zDbHeader[17]<<16);
//判断页面大小是否小于512或大于65535
//SQLITE_MAX_PAGE_SIZE,65535
if( pBt->pageSize<512 || pBt->pageSize>SQLITE_MAX_PAGE_SIZE
|| ((pBt->pageSize-1)&pBt->pageSize)!=0 ){
pBt->pageSize = 0;
//SQLITE_OMIT_AUTOVACUUM,禁止自动整理数据库文件以缩小文件空间占用
#ifndef SQLITE_OMIT_AUTOVACUUM
/* If the magic name ":memory:" will create an in-memory database, then
** leave the autoVacuum mode at 0 (do not auto-vacuum), even if
** SQLITE_DEFAULT_AUTOVACUUM is true. On the other hand, if
** SQLITE_OMIT_MEMORYDB has been defined, then ":memory:" is just a
** regular file-name. In this case the auto-vacuum applies as per normal.
*/
//内存数据库不存在自动vacuum
if( zFilename && !isMemdb ){
pBt->autoVacuum = (SQLITE_DEFAULT_AUTOVACUUM ? 1 : 0);
pBt->incrVacuum = (SQLITE_DEFAULT_AUTOVACUUM==2 ? 1 : 0);
}
#endif
//每个页面末尾保留字节数
nReserve = 0;
}else{
/* EVIDENCE-OF: R-37497-42412 The size of the reserved region is
** determined by the one-byte unsigned integer found at an offset of 20
** into the database file header. */
//页面大小正常,从头信息中获取相应配置
//0x14: 每页底部保留bytes数(默认是0)
nReserve = zDbHeader[20];
//设置页面大小固定标志
//BTS_PAGESIZE_FIXED,页面大小固定,不能改变
pBt->btsFlags |= BTS_PAGESIZE_FIXED;
#ifndef SQLITE_OMIT_AUTOVACUUM
//设置autovacuum模式,位于0x34-0x37
pBt->autoVacuum = (get4byte(&zDbHeader[36 + 4*4])?1:0);
//设置增量vacuum模式,位于0x40-0x43
pBt->incrVacuum = (get4byte(&zDbHeader[36 + 7*4])?1:0);
#endif
}
//设置页面大小
rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, nReserve);
if( rc ) goto btree_open_out;
pBt->usableSize = pBt->pageSize - nReserve;
assert( (pBt->pageSize & 7)==0 ); /* 8-byte alignment of pageSize */
#if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO)
/* Add the new BtShared object to the linked list sharable BtShareds.
*/
//允许共享内存,允许磁盘写入
//将新的BtShared加入共享内存列表
pBt->nRef = 1;
if( p->sharable ){
MUTEX_LOGIC( sqlite3_mutex *mutexShared; )
MUTEX_LOGIC( mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);)
if( SQLITE_THREADSAFE && sqlite3GlobalConfig.bCoreMutex ){
pBt->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_FAST);
if( pBt->mutex==0 ){
rc = SQLITE_NOMEM_BKPT;
goto btree_open_out;
}
}
sqlite3_mutex_enter(mutexShared);
//将pBt作为头结点插入sqlite3SharedCacheList列表
pBt->pNext = GLOBAL(BtShared*,sqlite3SharedCacheList);
GLOBAL(BtShared*,sqlite3SharedCacheList) = pBt;
sqlite3_mutex_leave(mutexShared);
}
#endif
}
#if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO)
/* If the new Btree uses a sharable pBtShared, then link the new
** Btree into the list of all sharable Btrees for the same connection.
** The list is kept in ascending order by pBt address.
*/
//如果新的btree使用共享pBtShared,需要把新的btree加入到相同连接的共享btree列表中
if( p->sharable ){
int i;
Btree *pSib;
for(i=0; i<db->nDb; i++){
if( (pSib = db->aDb[i].pBt)!=0 && pSib->sharable ){
//找到btree列表的第一个节点
while( pSib->pPrev ){ pSib = pSib->pPrev; }
//判断将p插入到数据库连接的pbt的位置
if( (uptr)p->pBt<(uptr)pSib->pBt ){
//头节点位置
p->pNext = pSib;
p->pPrev = 0;
pSib->pPrev = p;
}else{
//非头节点的位置
while( pSib->pNext && (uptr)pSib->pNext->pBt<(uptr)p->pBt ){
pSib = pSib->pNext;
}
p->pNext = pSib->pNext;
p->pPrev = pSib;
if( p->pNext ){
p->pNext->pPrev = p;
}
pSib->pNext = p;
}
break;
}
}
}
#endif
*ppBtree = p;
btree_open_out:
if( rc!=SQLITE_OK ){
if( pBt && pBt->pPager ){
sqlite3PagerClose(pBt->pPager, 0);
}
sqlite3_free(pBt);
sqlite3_free(p);
*ppBtree = 0;
}else{
sqlite3_file *pFile;
/* If the B-Tree was successfully opened, set the pager-cache size to the
** default value. Except, when opening on an existing shared pager-cache,
** do not change the pager-cache size.
*/
if( sqlite3BtreeSchema(p, 0, 0)==0 ){
sqlite3PagerSetCachesize(p->pBt->pPager, SQLITE_DEFAULT_CACHE_SIZE);
}
pFile = sqlite3PagerFile(pBt->pPager);
if( pFile->pMethods ){
sqlite3OsFileControlHint(pFile, SQLITE_FCNTL_PDB, (void*)&pBt->db);
}
}
if( mutexOpen ){
assert( sqlite3_mutex_held(mutexOpen) );
sqlite3_mutex_leave(mutexOpen);
}
assert( rc!=SQLITE_OK || sqlite3BtreeConnectionCount(*ppBtree)>0 );
return rc;
}
里面有几个小函数调用,下面看下
调用系统函数内存申请的再单独说下吧,调用层级多了点
获取字符串长度:
//获取字符串的长度,长度永远>0,但当长度大于0x3fffffff的时候,会返回0x3fffffff
//0x3fffffff,1073741823=1G - 1
int sqlite3Strlen30(const char *z){
if( z==0 ) return 0;
return 0x3fffffff & (int)strlen(z);
}
获取输入路径的全路径sqlite3OsFullPathname:
int sqlite3OsFullPathname(
sqlite3_vfs *pVfs,
const char *zPath,
int nPathOut,
char *zPathOut
){
DO_OS_MALLOC_TEST(0);
zPathOut[0] = 0;
return pVfs->xFullPathname(pVfs, zPath, nPathOut, zPathOut);
}
其中xFullPathname是一系列系统访问接口中的一个,与平台有关,这里大致看下:
以win平台为例,全文搜索一下,找到如下内容:
static sqlite3_vfs winVfs = {
3, /* iVersion */
sizeof(winFile), /* szOsFile */
SQLITE_WIN32_MAX_PATH_BYTES, /* mxPathname */
0, /* pNext */
"win32", /* zName */
&winAppData, /* pAppData */
winOpen, /* xOpen */
winDelete, /* xDelete */
winAccess, /* xAccess */
winFullPathname, /* xFullPathname */
winDlOpen, /* xDlOpen */
winDlError, /* xDlError */
winDlSym, /* xDlSym */
winDlClose, /* xDlClose */
winRandomness, /* xRandomness */
winSleep, /* xSleep */
winCurrentTime, /* xCurrentTime */
winGetLastError, /* xGetLastError */
winCurrentTimeInt64, /* xCurrentTimeInt64 */
winSetSystemCall, /* xSetSystemCall */
winGetSystemCall, /* xGetSystemCall */
winNextSystemCall, /* xNextSystemCall */
};
结构体struct sqlite3_vfs,结构如下:
struct sqlite3_vfs {
int iVersion; /* Structure version number (currently 3) */
int szOsFile; /* Size of subclassed sqlite3_file */
int mxPathname; /* Maximum file pathname length */
sqlite3_vfs *pNext; /* Next registered VFS */
const char *zName; /* Name of this virtual file system */
void *pAppData; /* Pointer to application-specific data */
int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
int flags, int *pOutFlags);
int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut);
void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void);
void (*xDlClose)(sqlite3_vfs*, void*);
int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
int (*xSleep)(sqlite3_vfs*, int microseconds);
int (*xCurrentTime)(sqlite3_vfs*, double*);
int (*xGetLastError)(sqlite3_vfs*, int, char *);
/*
** The methods above are in version 1 of the sqlite_vfs object
** definition. Those that follow are added in version 2 or later
*/
int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*);
/*
** The methods above are in versions 1 and 2 of the sqlite_vfs object.
** Those below are for version 3 and greater.
*/
int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr);
sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName);
const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName);
/*
** The methods above are in versions 1 through 3 of the sqlite_vfs object.
** New fields may be appended in future versions. The iVersion
** value will increment whenever this happens.
*/
};
大致可以看出,基本上都是函数指针,然后这些指针在os_init中进行初始化设置
返回到win平台,继续看winFullPathname函数的实现,函数内容太长了,主要是要适应各种平台
比如wince,winrt,cygwin等等,这里只列出关键部分,感兴趣的可以在os_win.c中查看
char *zTemp;
nByte = osGetFullPathNameA((char*)zConverted, 0, 0, 0);
if( nByte==0 ){
sqlite3_free(zConverted);
return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
"winFullPathname3", zRelative);
}
对于其中的osGetFullPathNameA,点进去:
#if !SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_ANSI)
{ "GetFullPathNameA", (SYSCALL)GetFullPathNameA, 0 },
#else
{ "GetFullPathNameA", (SYSCALL)0, 0 },
#endif
#define osGetFullPathNameA ((DWORD(WINAPI*)(LPCSTR,DWORD,LPSTR, \
LPSTR*))aSyscall[24].pCurrent)
这下明了了,就是win下的GetFullPathNameA函数
看到这里,应该基本上明白了sqlite跨平台底层实现的逻辑了
sqlite3_os_init是系统设置平台接口的地方,查找下调用的地方,找到如下函数,位于os.c中:
int sqlite3OsInit(void){
void *p = sqlite3_malloc(10);
if( p==0 ) return SQLITE_NOMEM_BKPT;
sqlite3_free(p);
return sqlite3_os_init();
}
那么sqlite3OsInit在哪里调用呢,继续查找,找到了如下代码:
int sqlite3_initialize(void){
......
if( rc==SQLITE_OK ){
sqlite3GlobalConfig.isPCacheInit = 1;
rc = sqlite3OsInit();
}
......
}
这下就比较清晰了