sqlite btree打开流程

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();
    }

    ......
}

这下就比较清晰了

猜你喜欢

转载自blog.csdn.net/ybn187/article/details/82183171