一:sql准备过程


在前面的分析中我们知道,sqlite3_open()为我们打开了数据库并准备了所要的内存空间,锁,vfs等。

接下来就分析sql是如何被解析器一步一步解析的。

java 开源sql性能分析工具_sql

上图是准备sql语句的过程分析图。

  1.1:sqlite3_prepare_v2函数:


该函数是准备的入口函数。

 传入了5个参数:

sqlite3 *db :          sqlite3_open()返回的数据库句柄

const char *zSql:     要准备的sql语句

int nBytes :          sql语句的 长度(strlen(sql))

sqlite3_stmt **ppStmt: 准备的语句存储该结构体对象中,可以认为是预编译语句的句柄,执行时需要传入这个句柄

const char **pzTail:   指向被解析的字符串的末尾

SQLite 现在提供两个版本的编译 API 函数:遗留的和现在使用的。

在遗留版本中,原始 SQL 文本没有保存在编译后的语句(sqlite3_stmt 结构)中,因此,如

果 schema 发生改变,sqlite3_step()会返回 SQLITE_SCHEMA。在新版本中,编译后的语句

中保存原始 SQL 文本,当遇到schema 改变时自动重新编译。sqlite3_prepare()函数中其实只包含一条对 sqlite3LockAndPrepare()的调用语句:

 

SQLITE_APIint sqlite3_prepare_v2(
  sqlite3 *db,              /* Database handle. 成功打开的数据库句柄*/
  constchar *zSql,         /* UTF-8 encoded SQL statement. UTF8编码的 SQL 语句 */
  intnBytes,               /* Length of zSql in bytes. 参数 sql 的字节数, 包含 '\0'  */
  sqlite3_stmt **ppStmt,    /* OUT: A pointer to the prepared statement 输出:预编译语句句柄 */
  constchar **pzTail       /* OUT: End of parsed string 输出:指向 sql 语句中未使用的部分*/
){
  int rc;
  
  rc = sqlite3LockAndPrepare(db,zSql,nBytes,SQLITE_PREPARE_SAVESQL,0,
                             ppStmt,pzTail);
  assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 );
  return rc;
}

 

1.2:sqlite3LockAndPrepare函数

 

该函数并没有做太多事情主要是多了两个参数,判断一下db是否合法。

调用sqlite3Prepare函数。


staticint sqlite3LockAndPrepare(
  sqlite3 *db,              /* Database handle. */
  constchar *zSql,         /* UTF-8 encoded SQL statement.(UTF-8 编码的 SQL 语句) */
  intnBytes,               /* Length of zSql in bytes. (zSql 的字节数)*/
  u32prepFlags,            /* Zero or more SQLITE_PREPARE_* flags(保存的sql文本) */
  Vdbe *pOld,               /* VM being reprepared */
  sqlite3_stmt **ppStmt,    /* OUT: A pointer to the prepared statement(指向已准备好的语句的指针 ) */
  constchar **pzTail       /* OUT: End of parsed string(未处理的 SQL 串) */
){
  int rc;
 
#ifdef SQLITE_ENABLE_API_ARMOR
  if( ppStmt==0 ) return SQLITE_MISUSE_BKPT;
#endif
  *ppStmt = 0;
  if( !sqlite3SafetyCheckOk(db)||zSql==0 ){//确定 db 指针的合法性
    returnSQLITE_MISUSE_BKPT;
  }
  sqlite3_mutex_enter(db->mutex);
  sqlite3BtreeEnterAll(db);
 db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail);//将 UTF-8 编码的 SQL 语句 zSql 编译成
  if( rc==SQLITE_SCHEMA ){    // 如果遇到 SCHEMA 改变,定案,再编译
    sqlite3ResetOneSchema(db, -1);
    sqlite3_finalize(*ppStmt);
    rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail);
  }
  sqlite3BtreeLeaveAll(db);
  sqlite3_mutex_leave(db->mutex);
  assert( rc==SQLITE_OK || *ppStmt==0 );
  return rc;
}


  

1.3: sqlite3Prepare函数

这个函数做了以下几个事:

1:加入解析器Parse

2:所需内存的初始化以及是否禁用后备内存

3:判断链接的数据库是否加锁,如果加锁需要解锁

4:zSqlCopy= sqlite3DbStrNDup(db, zSql, nBytes)语句复制了zsql。

5:调用sqlite3RunParser(&sParse,zSql, &zErrMsg);

还有的后面补上

 

 

staticint sqlite3Prepare(
  sqlite3 *db,              /* Database handle. */
  constchar *zSql,         /* UTF-8 encoded SQL statement. */
  intnBytes,               /* Length of zSql in bytes. */
  u32prepFlags,            /* Zero or more SQLITE_PREPARE_* flags */
  Vdbe *pReprepare,         /* VM being reprepared */
  sqlite3_stmt **ppStmt,    /* OUT: A pointer to the prepared statement (输出经过处理的准备语句)*/
  constchar **pzTail       /* OUT: End of parsed string */
){
  char *zErrMsg = 0;        /* Error message */
  int rc = SQLITE_OK;       /* Result code */
  int i;                    /* Loop counter */
  Parse sParse;             /* Parsing context(解析语境) */
 
  memset(&sParse, 0, PARSE_HDR_SZ);
  memset(PARSE_TAIL(&sParse), 0, PARSE_TAIL_SZ);//初始化内存空间
  sParse.pReprepare = pReprepare;
  assert( ppStmt && *ppStmt==0 );
  /* assert( !db->mallocFailed ); // not true with SQLITE_USE_ALLOCA */
  assert( sqlite3_mutex_held(db->mutex) );
 
  /* For a long-term use prepared statement avoid the use of
  ** lookaside memory.
     (长期使用事先准备好的声明避免后备存储器的使用。)
  */
  if( prepFlags & SQLITE_PREPARE_PERSISTENT ){
    sParse.disableLookaside++;//禁用后备内存 来使需要重复使用的准备语句可以多次使用
    db->lookaside.bDisable++;//同时禁止db中后备内存分配的参数
  }
 
  /* 
  **(检查以验证是否可以在所有数据库模式上获得读锁 .无法获得读锁,表明某些其他数据库连接持有写锁。
    这反过来意味着另一个连接对模式做出了未提交的更改。 )
  **(我们是否要继续和准备对未提交的模式更改的声明,如果这些模式更改随后回滚,并在其位置进行不同的更改
     然后,当准备好的语句运行时,模式cookie将无法检测模式更改。灾难会随之发生。 
  */
  for(i=0; i<db->nDb; i++) {//依此遍历连接的数据库
    Btree *pBt = db->aDb[i].pBt;
    if( pBt ){
      assert( sqlite3BtreeHoldsMutex(pBt) );
      rc = sqlite3BtreeSchemaLocked(pBt);//判断这个btree是否被锁
      if( rc ){ //如果是就提示该数据库已被加锁
        constchar *zDb = db->aDb[i].zDbSName;
        sqlite3ErrorWithMsg(db, rc, "database schema is locked: %s", zDb);
        testcase( db->flags & SQLITE_ReadUncommit );
        goto end_prepare;
      }
    }
  }
 
  sqlite3VtabUnlockList(db);//然后解锁
 
  sParse.db = db;
  if( nBytes>=0 && (nBytes==0 || zSql[nBytes-1]!=0) ){
    char *zSqlCopy;
    int mxLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; //准备语句最大长度
    testcase( nBytes==mxLen );
    testcase( nBytes==mxLen+1 );
    if( nBytes>mxLen ){//如果超过了这个长度就提示
      sqlite3ErrorWithMsg(db, SQLITE_TOOBIG, "statement too long");
      rc = sqlite3ApiExit(db, SQLITE_TOOBIG);
      goto end_prepare;//结束准备阶段
    }
    zSqlCopy = sqlite3DbStrNDup(db, zSql, nBytes);//复制zSql字符串
    if( zSqlCopy ){
      sqlite3RunParser(&sParse, zSqlCopy, &zErrMsg);
      sParse.zTail = &zSql[sParse.zTail-zSqlCopy];
      sqlite3DbFree(db, zSqlCopy);
    }else{
      sParse.zTail = &zSql[nBytes];
    }
  }else{
    sqlite3RunParser(&sParse, zSql, &zErrMsg);
  }
  assert( 0==sParse.nQueryLoop );
 
  if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK;
  if( sParse.checkSchema ){
    schemaIsValid(&sParse);
  }
  if( db->mallocFailed ){
    sParse.rc = SQLITE_NOMEM_BKPT;
  }
  if( pzTail ){
    *pzTail = sParse.zTail;
  }
  rc = sParse.rc;
 
#ifndef SQLITE_OMIT_EXPLAIN
  if( rc==SQLITE_OK && sParse.pVdbe && sParse.explain ){
    staticconstchar * const azColName[] = {
       "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment",
       "selectid", "order", "from", "detail"
    };
    int iFirst, mx;
    if( sParse.explain==2 ){
      sqlite3VdbeSetNumCols(sParse.pVdbe, 4);
      iFirst = 8;
      mx = 12;
    }else{
      sqlite3VdbeSetNumCols(sParse.pVdbe, 8);
      iFirst = 0;
      mx = 8;
    }
    for(i=iFirst; i<mx; i++){
      sqlite3VdbeSetColName(sParse.pVdbe, i-iFirst, COLNAME_NAME,
                            azColName[i], SQLITE_STATIC);
    }
  }
#endif
 
  if( db->init.busy==0 ){
    sqlite3VdbeSetSql(sParse.pVdbe, zSql, (int)(sParse.zTail-zSql), prepFlags);
  }
  if( sParse.pVdbe && (rc!=SQLITE_OK || db->mallocFailed) ){
    sqlite3VdbeFinalize(sParse.pVdbe);
    assert(!(*ppStmt));
  }else{
    *ppStmt = (sqlite3_stmt*)sParse.pVdbe;
  }
 
  if( zErrMsg ){
    sqlite3ErrorWithMsg(db, rc, "%s", zErrMsg);
    sqlite3DbFree(db, zErrMsg);
  }else{
    sqlite3Error(db, rc);
  }
 
  /* Delete any TriggerPrg structures allocated while parsing this statement. */
  while( sParse.pTriggerPrg ){
    TriggerPrg *pT = sParse.pTriggerPrg;
    sParse.pTriggerPrg = pT->pNext;
    sqlite3DbFree(db, pT);
  }
 
end_prepare:
 
  sqlite3ParserReset(&sParse);
  rc = sqlite3ApiExit(db, rc);
  assert( (rc&db->errMask)==rc );
  return rc;
}

 

1.4:sqlite3RunParser函数

sqlite3RunParser位于token.c文件中,它是进行SQL语句分析的入口,它调用sqlite3GetToken对SQL语句zSql进行分词,然后调用sqlite3Parser进行语法分析。而sqlite3Parser在语法规则发生规约时调用相应的opcode生成子例程,生成opcode。

在给定的SQL字符串上运行解析器.解析器结构传入。

  功能:在给定的 SQL 字符串上执行分析器。传入一个 parser 结构。返回一个 SQLITE_状态

码。如果有错误发生,将错误信息写入*pzErrMsg。

本函数内部是一个循环语句,每次循环处理一个词,根据词的类型做出不同的处理。如果是

正经的词(不是空格什么的),都会调用 sqlite3Parser()函数对其进行分析。

 

SQLITE_PRIVATEint sqlite3RunParser(Parse *pParse, constchar *zSql, char **pzErrMsg){

 

  int nErr = 0;                   /* Number of errors encountered(遇到的错误数) */

  void *pEngine;                  /* The LEMON-generated LALR(1) parser(lemon算法解析器) */

  int n = 0;                      /* Length of the next token token(下一个token的长度) */

  int tokenType;                  /* type of the next token (下一个token的类型)*/

  int lastTokenParsed = -1;       /* type of the previous token (上一个token的类型)*/

  sqlite3 *db = pParse->db;       /* The database connection */

  int mxSqlLen;                   /* Max length of an SQL string(sql字符串的最大长度) */

#ifdefsqlite3Parser_ENGINEALWAYSONSTACK

  yyParser sEngine;    /* Space to hold the Lemon-generated Parser object(保存lemon生成的解析器对象的空间 ) */

#endif

 

  assert( zSql!=0 );

  mxSqlLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH];//     mxSqlLen 1000000000   int

  if( db->nVdbeActive==0 ){//虚拟机活动数量为0

    db->u1.isInterrupted = 0;//sqlite3_interrupt不被执行(我猜是中断执行)

  }

  pParse->rc = SQLITE_OK;

  pParse->zTail = zSql;

  assert( pzErrMsg!=0 );

  /* sqlite3ParserTrace(stdout, "parser: "); */

#ifdefsqlite3Parser_ENGINEALWAYSONSTACK

  pEngine = &sEngine;

  sqlite3ParserInit(pEngine);//初始化已分配的解析器(初始化解析器对象各个字段)

#else

  pEngine = sqlite3ParserAlloc(sqlite3Malloc);

  if( pEngine==0 ){

    sqlite3OomFault(db);

    return SQLITE_NOMEM_BKPT;

  }

#endif

  assert( pParse->pNewTable==0 );

  assert( pParse->pNewTrigger==0 );

  assert( pParse->nVar==0 );

  assert( pParse->pVList==0 );

  while( 1 ){

    if( zSql[0]!=0 ){// zSql[0]表示一个token 开头(比如select就为s)

   u8*)zSql, &tokenType);

      mxSqlLen -= n;

      if( mxSqlLen<0 ){

        pParse->rc = SQLITE_TOOBIG;

        break;

      }

    }else{

  

      if( lastTokenParsed==TK_SEMI ){

        tokenType = 0;

      }elseif( lastTokenParsed==0 ){

        break;

      }else{

        tokenType = TK_SEMI;

      }

      zSql -= n;

    }

    if( tokenType>=TK_SPACE ){

      assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL );

      if( db->u1.isInterrupted ){

        pParse->rc = SQLITE_INTERRUPT;

        break;

      }

      if( tokenType==TK_ILLEGAL ){

        sqlite3ErrorMsg(pParse, "unrecognized token: \"%.*s\"", n, zSql);

        break;

      }

      zSql += n;

    }else{

      pParse->sLastToken.z = zSql;//0x0070808f "id,name from stu" const char *

      pParse->sLastToken.n = n;

    pParse->sLastToken, pParse);

      lastTokenParsed = tokenType;

      zSql += n;

      if( pParse->rc!=SQLITE_OK || db->mallocFailed ) break;

    }

  }

  assert( nErr==0 );

  pParse->zTail = zSql;

#ifdef YYTRACKMAXSTACKDEPTH

  sqlite3_mutex_enter(sqlite3MallocMutex());

  sqlite3StatusHighwater(SQLITE_STATUS_PARSER_STACK,

      sqlite3ParserStackPeak(pEngine)

  );

  sqlite3_mutex_leave(sqlite3MallocMutex());

#endif/* YYDEBUG */

#ifdefsqlite3Parser_ENGINEALWAYSONSTACK

  sqlite3ParserFinalize(pEngine);

#else

  sqlite3ParserFree(pEngine, sqlite3_free);

#endif

  if( db->mallocFailed ){

    pParse->rc = SQLITE_NOMEM_BKPT;

  }

  if( pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE && pParse->zErrMsg==0 ){

    pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc));

  }

  assert( pzErrMsg!=0 );

  if( pParse->zErrMsg ){

    *pzErrMsg = pParse->zErrMsg;

    sqlite3_log(pParse->rc, "%s", *pzErrMsg);

    pParse->zErrMsg = 0;

    nErr++;

  }

  if( pParse->pVdbe && pParse->nErr>0 && pParse->nested==0 ){

    sqlite3VdbeDelete(pParse->pVdbe);

    pParse->pVdbe = 0;

  }

#ifndef SQLITE_OMIT_SHARED_CACHE

  if( pParse->nested==0 ){

    sqlite3DbFree(db, pParse->aTableLock);

    pParse->aTableLock = 0;

    pParse->nTableLock = 0;

  }

#endif

#ifndef SQLITE_OMIT_VIRTUALTABLE

  sqlite3_free(pParse->apVtabLock);

#endif

 

  if( !IN_DECLARE_VTAB ){

  

 

    sqlite3DeleteTable(db, pParse->pNewTable);

  }

 

  if( pParse->pWithToFree ) sqlite3WithDelete(db, pParse->pWithToFree);

  sqlite3DeleteTrigger(db, pParse->pNewTrigger);

  sqlite3DbFree(db, pParse->pVList);

  while( pParse->pAinc ){

    AutoincInfo *p = pParse->pAinc;

    pParse->pAinc = p->pNext;

    sqlite3DbFreeNN(db, p);

  }

  while( pParse->pZombieTab ){

    Table *p = pParse->pZombieTab;

    pParse->pZombieTab = p->pNextZombie;

    sqlite3DeleteTable(db, p);

  }

  assert( nErr==0 || pParse->rc!=SQLITE_OK );

  return nErr;

}

 

这个函数里面比较重要的三个函数我已经加粗了。

下面我说一下解析时是如何循环的。

 n = sqlite3GetToken((u8*)zSql, &tokenType);

这句代码就是获取sql语句第一个词的长度,比如有一个sql语句select id name from stu;

通过while循环获取第一个词select的长度为6。然后依次循环直到结束。

 

第一次循环zSql[0]的数为s,及sql预语句的第一个关键字的第一个字符。

java 开源sql性能分析工具_sqlite_02

然后获取n为6及select的长度,mxSqlLen-= n;这条语句是让最大长度减当前n的值。

java 开源sql性能分析工具_sql_03

后面就将n的大小和sql语句传个解析对象和Token

然后执行sqlite3Parser(pEngine,tokenType, pParse->sLastToken, pParse);函数进行解析

java 开源sql性能分析工具_java 开源sql性能分析工具_04