SQLite3源程序分析之查詢處理及優化


前言

  查詢處理及優化是關系數據庫得以流行的根本原因,也是關系數據庫系統最核心的技術之一。SQLite的查詢處理模塊很精致,而且很容易移植到不支持SQL的存儲引擎(Berkeley DB最新的版本已經將其完整的移植過來)。
  查詢處理一般來說,包括詞法分析、語法分析、語義分析、生成執行計划以及執行計划幾個部分。SQLite的詞法分析器是手工寫的(比較簡單),語法分析器由Lemon生成,語義分析主要是進行語義方面的一些檢查,比如table是否存在等。而執行計划的生成及執行是最核心的兩部分,也是相對比較復雜、有點技術含量的部分。SQLite的執行計划采用了虛擬機的思想,實際上,這種基於虛擬機的思想並非SQLite所獨有,但是,SQLite將其發揮到了極致,它生成的執行計划非常詳細,而且易讀(不得不佩服D. Richard Hipp在編譯理論方面的功底)。

1、語法分析——語法樹

  語法分析的主要任務是對用戶輸入的SQL語句進行語法檢查,然后生成一個包含所有信息的語法樹。對於SELECT語句,這個語法樹最終由結構體Select表示:

struct Select {
  ExprList *pEList;      /* The fields of the result 結果的字段 */
  u8 op;                 /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */
  char affinity;         /* MakeRecord with this affinity for SRT_Set */
  u16 selFlags;          /* Various SF_* values */
  SrcList *pSrc;         /* The FROM clause */
  Expr *pWhere;          /* The WHERE clause */
  ExprList *pGroupBy;    /* The GROUP BY clause */
  Expr *pHaving;         /* The HAVING clause */
  ExprList *pOrderBy;    /* The ORDER BY clause */
  Select *pPrior;        /* Prior select in a compound select statement 在一個復合選擇語句中的優先選擇 */
  Select *pNext;         /* Next select to the left in a compound */
  Select *pRightmost;    /* Right-most select in a compound select statement 在一個復合選擇語句中的最右邊的選擇*/
  Expr *pLimit;          /* LIMIT expression. NULL means not used. */
  Expr *pOffset;         /* OFFSET expression. NULL means not used. */
  int iLimit, iOffset;   /* Memory registers holding LIMIT & OFFSET counters */
  int addrOpenEphm[3];   /* OP_OpenEphem opcodes related to this select */
};

  該結構體中,pEList是結果列的語法樹;pSrc為FROM子句的語法樹;pWhere為WHERE部分的語法樹。

  select語法分析最終在sqlite3SelectNew中完成:

Select *sqlite3SelectNew(
  Parse *pParse,        /* Parsing context 解析上下文 */
  ExprList *pEList,     /* which columns to include in the result 在結果中包含哪些列 */
  SrcList *pSrc,        /* the FROM clause -- which tables to scan */
  Expr *pWhere,         /* the WHERE clause */
  ExprList *pGroupBy,   /* the GROUP BY clause */
  Expr *pHaving,        /* the HAVING clause */
  ExprList *pOrderBy,   /* the ORDER BY clause */
  int isDistinct,       /* true if the DISTINCT keyword is present */
  Expr *pLimit,         /* LIMIT value.  NULL means not used */
  Expr *pOffset         /* OFFSET value.  NULL means no offset */
){
  Select *pNew;
  Select standin;
  sqlite3 *db = pParse->db;
  pNew = sqlite3DbMallocZero(db, sizeof(*pNew) );
  assert( db->mallocFailed || !pOffset || pLimit ); /* OFFSET implies LIMIT */
  if( pNew==0 ){
    pNew = &standin;
    memset(pNew, 0, sizeof(*pNew));
  }
  if( pEList==0 ){
    pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db,TK_ALL,0));
  }
  pNew->pEList = pEList;
  pNew->pSrc = pSrc;
  pNew->pWhere = pWhere;
  pNew->pGroupBy = pGroupBy;
  pNew->pHaving = pHaving;
  pNew->pOrderBy = pOrderBy;
  pNew->selFlags = isDistinct ? SF_Distinct : 0;
  pNew->op = TK_SELECT;
  pNew->pLimit = pLimit;
  pNew->pOffset = pOffset;
  assert( pOffset==0 || pLimit!=0 );
  pNew->addrOpenEphm[0] = -1;
  pNew->addrOpenEphm[1] = -1;
  pNew->addrOpenEphm[2] = -1;
  if( db->mallocFailed ) {
    clearSelect(db, pNew);
    if( pNew!=&standin ) sqlite3DbFree(db, pNew);
    pNew = 0;
  }
  return pNew;
}

  以上函數主要是將之前得到的各個子語法樹匯總到Select結構體,並根據該結構,進行語義分析及執行計划的生成等工作。

  示例(貫穿全文)

explain select s.sname,c.cname,sc.grade from students s join sc join course c on s.sid=sc.sid and sc.cid = c.cid;
0|Trace|0|0|0||00|
1|Goto|0|35|0||00|
//////////////////////////(1)////////////////////////////
2|OpenRead|0|3|0|2|00|students  #打開students表 3|OpenRead|1|7|0|3|00|sc      #打開sc表 4|OpenRead|3|8|0|keyinfo(2,BINARY,BINARY)|00|sqlite_autoindex_sc_1 #sc的索引 5|OpenRead|2|5|0|2|00|course    #打開course表 6|OpenRead|4|6|0|keyinfo(1,BINARY)|00|sqlite_autoindex_course_1    #course的索引 //////////////////////////(2)//////////////////////////////
7|Rewind|0|29|0||00|         #將游標p0定位到students表的第一條記錄
8|Column|0|0|1||00|students.sid  #取出第0列,寫到r1 9|IsNull|1|28|0||00|
10|Affinity|1|1|0|d|00|
11|SeekGe|3|28|1|1|00|        #將游標p3定位到sc索引>=r1的記錄處
12|IdxGE|3|28|1|1|01|
13|IdxRowid|3|2|0||00|
14|Seek|1|2|0||00|
15|Column|3|1|3||00|sc.cid      #讀取sc.cid到r3 16|IsNull|3|27|0||00|
17|Affinity|3|1|0|d|00|
18|SeekGe|4|27|3|1|00|        #將游標p4定位到course索引>=r3的記錄處
19|IdxGE|4|27|3|1|01|
20|IdxRowid|4|4|0||00|
21|Seek|2|4|0||00|
///////////////////////////(3)//////////////////////////////
22|Column|0|1|5||00|students.sname #從游標p0取出第1列 (sname) 23|Column|2|1|6||00|course.cname  #從游標p2取出第1列 (cname) 24|Column|1|2|7||00|sc.grade     #從游標p1取出第2列 (grade) 25|ResultRow|5|3|0||00|
///////////////////////////(4)///////////////////////////////
26|Next|4|19|0||00|
27|Next|3|12|0||00|
28|Next|0|8|0||01|
29|Close|0|0|0||00|
30|Close|1|0|0||00|
31|Close|3|0|0||00|
32|Close|2|0|0||00|
33|Close|4|0|0||00|
//////////////////////////(5)//////////////////////////////////
34|Halt|0|0|0||00|
35|Transaction|0|0|0||00|
36|VerifyCookie|0|7|0||00|
37|TableLock|0|3|0|students|00|
38|TableLock|0|7|0|sc|00|
39|TableLock|0|5|0|course|00|
40|Goto|0|2|0||00|

  該SQL語句生成的語法樹如下:

FROM部分

第一個表項:

表名zName =”stduents”,zAlias=”s”,jointype = 0

第二個表項:

jointype = 1(JT_INNER)

第三個表項:

jointype = 1(JT_INNER)

WHERE部分(結點類型為Expr的一棵二叉樹)

2、生成執行計划(語法樹到OPCODE)

  Select的執行計划在sqlite3Select中完成:

int sqlite3Select(
  Parse *pParse,         /* The parser context */
  Select *p,             /* SELECT語法樹 */
  SelectDest *pDest      /* 如何處理結果集 */
)

  該函數先對SQL語句進行語義分析,再進行優化,最后生成執行計划。

  對於上面的SQL語句,生成的執行計划(虛擬機opcode)大致分成5部分,前4部分都在sqlite3Select()中生成,它主要調用了以下幾個函數:

  其中(1)、(2)在sqlite3WhereBegin()中生成,(2)即所謂的查詢優化處理;(3)在 selectInnerLoop中生成;(4)在sqlite3WhereEnd中生成;(5)在sqlite3FinishCoding中完成。 

 1)sqlite3WhereBegin

  該函數是查詢處理最為核心的函數,它主要完成where部分的優化及相關opcode的生成。

WhereInfo *sqlite3WhereBegin(
  Parse *pParse,        /* The parser context */
  SrcList *pTabList,    /* A list of all tables to be scanned 要掃描的所有表的列表*/
  Expr *pWhere,         /* The WHERE clause */
  ExprList **ppOrderBy, /* An ORDER BY clause, or NULL */
  u16 wctrlFlags        /* One of the WHERE_* flags defined in sqliteInt.h */
)

  pTabList是由分析器對FROM部分生成的語法樹,它包含FROM語句中的表的信息;pWhere是WHERE部分的語法樹,它包含WHERE中所有表達式的信息;ppOrderBy對應ORDER BY子句。

  SQLite的查詢優化簡單而精致,在sqlite3WhereBegin函數中,即可完成所有的優化處理。查詢優化的基本理念就是嵌套循環(nested loop),SELECT語句的FROM子句的每個表對應一層循環(INSERT和UPDATE語句對應只有一個表)。例如:

SELECT * FROM t1, t2, t3 WHERE ...;

  進行如下操作:

foreach row1 in t1 do       \    Code generated
  foreach row2 in t2 do      |-- by sqlite3WhereBegin()
    foreach row3 in t3 do   /
      ...
    end                     \    Code generated
  end                        |-- by sqlite3WhereEnd()
end                         /

  而對於每一層的優化,基本的理念就是分析WHERE子句中是否有表達式能夠使用該層循環的表的索引。

  SQLite有三種基本的掃描策略:

   ① 全表掃描,這種情況通常出現在沒有WHERE子句時;

   ② 基於索引掃描,這種情況通常出現在表有索引,而且WHERE中的表達式又能夠使用該索引的情況;

   ③ 基本rowid的掃描,這種情況通常出現在WHERE表達式中含有rowid的條件。(該情況實際上也是對表進行掃描,SQLite以rowid為聚簇索引)

  第一種情況比較簡單,第三種情況與第二種情況沒有本質的差別。下面就第二種情況進行詳細討論。

  以下為sqlite3WhereBegin的關鍵代碼:

/*分析where子句的所有表達式
**如果表達式的形式為X <op> Y,則增加一個Y <op> X形式的虛Term,並在后面進行單獨分析
*/ exprAnalyzeAll(pTabList, pWC); WHERETRACE(("*** Optimizer Start ***\n")); //優化開始 for(i=iFrom=0, pLevel=pWInfo->a; i<nTabList; i++, pLevel++){ WhereCost bestPlan; /* Most efficient plan seen so far 迄今為止最有效的計划 */ Index *pIdx; /* Index for FROM table at pTabItem */ int j; /* For looping over FROM tables 從表循環*/ int bestJ = -1; /* The value of j */ Bitmask m; /* Bitmask value for j or bestJ */ int isOptimal; /* Iterator for optimal/non-optimal search 優化/非優化搜索的迭代器 */ memset(&bestPlan, 0, sizeof(bestPlan)); bestPlan.rCost = SQLITE_BIG_DBL; /*進行兩次掃描:*/   //如果第一次掃描沒有找到優化的掃描策略,此時,isOptimal==0,bestJ==-1,則進行第二次掃描 for(isOptimal=1; isOptimal>=0 && bestJ<0; isOptimal--){     //第一次掃描的mask==0,表示所有表都已經准備好 Bitmask mask = (isOptimal ? 0 : notReady); assert( (nTabList-iFrom)>1 || isOptimal ); for(j=iFrom, pTabItem=&pTabList->a[j]; j<nTabList; j++, pTabItem++){ int doNotReorder; /* True if this table should not be reordered 如果該表不應該被重新排序為True */ WhereCost sCost; /* Cost information from best[Virtual]Index() */ ExprList *pOrderBy; /* ORDER BY clause for index to optimize */      //對於左連接和交叉連接,不能改變嵌套的順序 doNotReorder = (pTabItem->jointype & (JT_LEFT|JT_CROSS))!=0; if( j!=iFrom && doNotReorder ) //如果j==iFrom,仍要進行優化處理(此時,是第一次處理iFrom項) break; m = getMask(pMaskSet, pTabItem->iCursor); if( (m & notReady)==0 ){//如果該pTabItem已經進行處理,則不需要再處理 if( j==iFrom ) iFrom++; continue; } pOrderBy = ((i==0 && ppOrderBy )?*ppOrderBy:0); { //對一個表(pTabItem),找到它的可用於本次查詢的最好的索引,sCost返回對應的代價 bestBtreeIndex(pParse, pWC, pTabItem, mask, pOrderBy, &sCost); } if( (sCost.used&notReady)==0 && (j==iFrom || sCost.rCost<bestPlan.rCost) ){ bestPlan = sCost; bestJ = j; //如果bestJ>=0,表示找到了優化的掃描策略 } if( doNotReorder ) break; }//end for }//end for WHERETRACE(("*** Optimizer selects table %d for loop %d\n", bestJ, pLevel-pWInfo->a)); if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 ){//不需要進行排序操作 *ppOrderBy = 0; }   //設置該層選用的查詢策略   andFlags &= bestPlan.plan.wsFlags;   pLevel->plan = bestPlan.plan;   //如果可以使用索引,則設置索引對應的游標的下標   if( bestPlan.plan.wsFlags & WHERE_INDEXED ){ pLevel->iIdxCur = pParse->nTab++;   }else{ pLevel->iIdxCur = -1;   }   notReady &= ~getMask(pMaskSet, pTabList->a[bestJ].iCursor);   //該層對應的FROM的表項,即該層循環是對哪個表進行的操作   pLevel->iFrom = (u8)bestJ; } //優化結束 WHERETRACE(("*** Optimizer Finished ***\n"));

  優化部分的代碼的基本算法如下:

foreach  level  in all_levels
bestPlan.rCost = SQLITE_BIG_DBL
foreach table in tables that not handled
{
    //計算where中表達式能使用其索引的策略及代價rCost
    If(sCost.rCost < bestPlan.rCost)
      bestPlan = sCost
}
level.plan = bestPlan

  該算法本質上是一個貪婪算法(greedy algorithm)。其中,bestBtreeIndex(pParse, pWC, pTabItem, mask, pOrderBy, &sCost)是pParse對應的表針對where子句的表達式分析查詢策略的核心函數。

  對於之前的示例,經過以上優化處理后,得到的查詢策略分3層循環,最外層是students表,全表掃描;中間層是sc表,利用索引sqlite_autoindex_sc_1,即sc的key對應的索引;內層是course表,利用索引sqlite_autoindex_course_1。

  之后,開始生成(1)、(2)兩部分opcode。

  其中(1)的opcode由以下代碼生成:

//生成打開表的指令
if( (pLevel->plan.wsFlags & WHERE_IDX_ONLY)==0
  && (wctrlFlags & WHERE_OMIT_OPEN)==0 ){
  //pTabItem->iCursor為表對應的游標下標
  int op = pWInfo->okOnePass ? OP_OpenWrite : OP_OpenRead;
  sqlite3OpenTable(pParse, pTabItem->iCursor, iDb, pTab, op);
}

//生成打開索引的指令
if( (pLevel->plan.wsFlags & WHERE_INDEXED)!=0 ){
  Index *pIx = pLevel->plan.u.pIdx;
  KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIx);
  int iIdxCur = pLevel->iIdxCur; //索引對應的游標下標
  sqlite3VdbeAddOp4(v, OP_OpenRead, iIdxCur, pIx->tnum, iDb,(char*)pKey, P4_KEYINFO_HANDOFF);
  VdbeComment((v, "%s", pIx->zName));
}

  而(2)的opcode由以下代碼生成:

 notReady = ~(Bitmask)0;
  for(i=0; i<nTabList; i++){ 
    //核心代碼,從最外層向最內層,為每一層循環生成opcode
    notReady = codeOneLoopStart(pWInfo, i, wctrlFlags, notReady);
    pWInfo->iContinue = pWInfo->a[i].addrCont;
  }

  其中codeOneLoopStart(pWInfo, i, wctrlFlags, notReady)函數,根據優化分析得到的結果生成每層循環的opcode:

static Bitmask codeOneLoopStart(
  WhereInfo *pWInfo,   /* Complete information about the WHERE clause */
  int iLevel,          /* Which level of pWInfo->a[] should be coded */
  u16 wctrlFlags,      /* One of the WHERE_* flags defined in sqliteInt.h */
  Bitmask notReady     /* Which tables are currently available */
)

  codeOneLoopStart針對5種不同的查詢策略,生成各自不同的opcode:

if( pLevel->plan.wsFlags & WHERE_ROWID_EQ ){  //rowid的等值查詢
...
}else if( pLevel->plan.wsFlags & WHERE_ROWID_RANGE ){  //rowid的范圍查詢
...
}else if( pLevel->plan.wsFlags & (WHERE_COLUMN_RANGE|WHERE_COLUMN_EQ) ){  //使用索引的等值/范圍查詢
...
}if( pLevel->plan.wsFlags & WHERE_MULTI_OR ){  //or
...
}else{  //全表掃描
...
}

  其中,全表掃描如下

static const u8 aStep[] = { OP_Next, OP_Prev };
static const u8 aStart[] = { OP_Rewind, OP_Last };
pLevel->op = aStep[bRev];
pLevel->p1 = iCur;
pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrBrk); //生成OP_Rewind/OP_Last指令
pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP;

  示例中最外層循環students是全表掃描,生成指令7。

  其中,利用索引的等值/范圍查詢:

  對於示例:中間循環sc表,用到索引,指令8~14是對應的opcode。
       內層循環course表,也用到索引,指令15~21是對應的opcode。

  在通用數據庫中,連接操作會生成所謂的結果集(用臨時表存儲)。而SQLite不會生成中間結果集,例如示例中,會分別對students、sc和course表各分配一個游標,每次調用接口sqlite3_step時,游標根據where條件分別定位到各自的記錄,然后取出查詢輸出列的數據,放到用於存放結果的寄存器中(如示例(3)中的opcode)。所以在SQLite中,必須不斷調用sqlite3_step才能讀取所有記錄。

 2)selectInnerLoop

  該函數主要生成輸出結果列的opcode,即示例(3)中的opcode。

 3)sqlite3WhereEnd

  該函數主要完成嵌套循環的收尾工作的opcode的生成,為每層循環生成OP_Next/OP_Prev,以及關閉表和索引游標的OP_Close。

3、SQLite的代價模型

  再看bestBtreeIndex函數,其完成查詢代價的計算以及查詢策略的確定。

  SQLite采用基於代價的優化。根據處理查詢時CPU和磁盤I/O的代價,主要考慮以下一些因素:
   A、查詢讀取的記錄數;
   B、結果是否排序(這可能會導致使用臨時表);
   C、是否需要訪問索引和原表。

static void bestBtreeIndex(
  Parse *pParse,              /* The parsing context */
  WhereClause *pWC,           /* The WHERE clause */
  struct SrcList_item *pSrc,  /* The FROM clause term to search */
  Bitmask notReady,           /* Mask of cursors that are not available */
  ExprList *pOrderBy,         /* The ORDER BY clause */
  WhereCost *pCost            /* Lowest cost query plan */
)

  該函數的主要工作就是輸出pCost,它包含查詢策略信息及相應的代價。

  其核心算法如下

//遍歷其所有索引,找到一個代價最小的索引  
for(; pProbe; pIdx=pProbe=pProbe->pNext){
    const unsigned int * const aiRowEst = pProbe->aiRowEst;
    double cost;                /* Cost of using pProbe */
    double nRow;                /* Estimated number of rows in result set */
    int rev;                    /* True to scan in reverse order */
    int wsFlags = 0;
    Bitmask used = 0;   //該表達式使用的表的位碼

    int nEq;           //可以使用索引的等值表達式的個數
    int bInEst = 0;     //如果存在 x IN (SELECT...),則設為true
    int nInMul = 1;     //處理IN子句
    int nBound = 100;   //估計需要掃描的表中的元素,100表示需要掃描整個表,范圍條件意味着只需要掃描表的某一部分
    int bSort = 0;      //是否需要排序
    int bLookup = 0;    //如果對索引中的每個列,需要對應的表進行查詢,則為true

    /* Determine the values of nEq and nInMul */
    //計算nEq和nInMul值
    for(nEq=0; nEq<pProbe->nColumn; nEq++){
      WhereTerm *pTerm; /* A single term of the WHERE clause */
      int j = pProbe->aiColumn[nEq];
      pTerm = findTerm(pWC, iCur, j, notReady, eqTermMask, pIdx);
      if( pTerm==0 ) //如果該條件在索引中找不到,則break
          break;
      wsFlags |= (WHERE_COLUMN_EQ|WHERE_ROWID_EQ);
      if( pTerm->eOperator & WO_IN ){
        Expr *pExpr = pTerm->pExpr;
        wsFlags |= WHERE_COLUMN_IN;
        if( ExprHasProperty(pExpr, EP_xIsSelect) ){ //IN (SELECT...)
          nInMul *= 25;
          bInEst = 1;
        }else if( pExpr->x.pList ){
          nInMul *= pExpr->x.pList->nExpr + 1;
        }
      }else if( pTerm->eOperator & WO_ISNULL ){
        wsFlags |= WHERE_COLUMN_NULL;
      }
      used |= pTerm->prereqRight; //設置該表達式使用的表的位碼
    }

    //計算nBound值
    if( nEq<pProbe->nColumn ){//考慮不能使用索引的列
      int j = pProbe->aiColumn[nEq];
      if( findTerm(pWC, iCur, j, notReady, WO_LT|WO_LE|WO_GT|WO_GE, pIdx) ){
        WhereTerm *pTop = findTerm(pWC, iCur, j, notReady, WO_LT|WO_LE, pIdx);
        WhereTerm *pBtm = findTerm(pWC, iCur, j, notReady, WO_GT|WO_GE, pIdx);//>=
        
        //估計范圍條件的代價
        whereRangeScanEst(pParse, pProbe, nEq, pBtm, pTop, &nBound);
        if( pTop ){
          wsFlags |= WHERE_TOP_LIMIT;
          used |= pTop->prereqRight;
        }
        if( pBtm ){
          wsFlags |= WHERE_BTM_LIMIT;
          used |= pBtm->prereqRight;
        }
        wsFlags |= (WHERE_COLUMN_RANGE|WHERE_ROWID_RANGE);
      }
    }else if( pProbe->onError!=OE_None ){//所有列都能使用索引
      if( (wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_NULL))==0 ){
        wsFlags |= WHERE_UNIQUE;
      }
    }

    if( pOrderBy ){//處理order by
      if( (wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_NULL))==0
        && isSortingIndex(pParse,pWC->pMaskSet,pProbe,iCur,pOrderBy,nEq,&rev)
      ){
        wsFlags |= WHERE_ROWID_RANGE|WHERE_COLUMN_RANGE|WHERE_ORDERBY;
        wsFlags |= (rev ? WHERE_REVERSE : 0);
      }else{
        bSort = 1;
      }
    }

    if( pIdx && wsFlags ){
      Bitmask m = pSrc->colUsed; //m為src使用的列的位圖
      int j;
      for(j=0; j<pIdx->nColumn; j++){
        int x = pIdx->aiColumn[j];
        if( x<BMS-1 ){
          m &= ~(((Bitmask)1)<<x); //將索引中列對應的位清0
        }
      }
      if( m==0 ){//如果索引包含src中的所有列,則只需要查詢索引即可
        wsFlags |= WHERE_IDX_ONLY;
      }else{
        bLookup = 1;//需要查詢原表
      }
    }

    //估計輸出行數,同時考慮IN運算
    nRow = (double)(aiRowEst[nEq] * nInMul);
    if( bInEst && nRow*2>aiRowEst[0] ){
      nRow = aiRowEst[0]/2;
      nInMul = (int)(nRow / aiRowEst[nEq]);
    }

    //代價為輸出的行數+二分查找的代價
    cost = nRow + nInMul*estLog(aiRowEst[0]);

    //考慮范圍條件影響
    nRow = (nRow * (double)nBound) / (double)100;
    cost = (cost * (double)nBound) / (double)100;

    //加上排序的代價:cost *log (cost)
    if( bSort ){
      cost += cost*estLog(cost);
    }

    //如果只查詢索引,則代價減半
    if( pIdx && bLookup==0 ){
      cost /= (double)2;
    }

    //如果當前的代價更小
    if( (!pIdx || wsFlags) && cost<pCost->rCost ){
      pCost->rCost = cost; //代價
      pCost->nRow = nRow;  //估計掃描的元組數
      pCost->used = used; //表達式使用的表的位圖
      pCost->plan.wsFlags = (wsFlags&wsFlagMask); //查詢策略標志(全表掃描,使用索引進行掃描)
      pCost->plan.nEq = nEq; //查詢策略使用等值表達式個數
      pCost->plan.u.pIdx = pIdx; //查詢策略使用的索引(全表掃描則為NULL)
    }

    //如果SQL語句存在INDEXED BY,則只考慮該索引
    if( pSrc->pIndex ) break;

    /* Reset masks for the next index in the loop */
    wsFlagMask = ~(WHERE_ROWID_EQ|WHERE_ROWID_RANGE);
    eqTermMask = idxEqTermMask;
  }

  SQLite的代價模型比較簡單,而通用數據庫一般是將基於規則的優化和基於代價的優化結合起來,更為復雜。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM