1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-12-24 14:17:58 +03:00

Improved ORDER BY optimization when outer loops of a join return a single row.

FossilOrigin-Name: 62225b4a4c4bfe1820ef54cb202edf2cd866429f
This commit is contained in:
drh
2012-09-29 19:10:29 +00:00
parent befda46598
commit 60441af4f2
4 changed files with 252 additions and 174 deletions

View File

@@ -1,5 +1,5 @@
C Disable\sthe\sbigfile\stests\son\sMacs.
D 2012-09-29T15:45:12.074
C Improved\sORDER\sBY\soptimization\swhen\souter\sloops\sof\sa\sjoin\sreturn\sa\ssingle\srow.
D 2012-09-29T19:10:29.355
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 5f4f26109f9d80829122e0e09f9cda008fa065fb
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -249,7 +249,7 @@ F src/vtab.c d8020c0a0e8ccc490ca449d7e665311b6e9f3ba9
F src/wal.c 5acb3e7bbd31f10ba39acad9ce6b399055337a9d
F src/wal.h 29c197540b19044e6cd73487017e5e47a1d3dac6
F src/walker.c 3d75ba73de15e0f8cd0737643badbeb0e002f07b
F src/where.c d836df3a2096c41c39e48ab5636f09f94ba02676
F src/where.c acc2ec5f6879721f332223da393777438ea5a606
F test/8_3_names.test 631ea964a3edb091cf73c3b540f6bcfdb36ce823
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
F test/aggnested.test 0be144b453e0622a085fae8665c32f5676708e00
@@ -635,6 +635,7 @@ F test/notnull.test cc7c78340328e6112a13c3e311a9ab3127114347
F test/null.test a8b09b8ed87852742343b33441a9240022108993
F test/openv2.test 0d3040974bf402e19b7df4b783e447289d7ab394
F test/orderby1.test 31c9865626046666e81cd22ecf8e1c24a4ea41b6
F test/orderby2.test 295d6639e1ce522195354b88ab298d7ede169736
F test/oserror.test 50417780d0e0d7cd23cf12a8277bb44024765df3
F test/pager1.test 2163c6ef119f497a71a84137c957c63763e640ab
F test/pager2.test 745b911dde3d1f24ae0870bd433dfa83d7c658c1
@@ -1017,7 +1018,7 @@ F tool/vdbe-compress.tcl f12c884766bd14277f4fcedcae07078011717381
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
F tool/win/sqlite.vsix 67d8a99aceb56384a81b3f30d6c71743146d2cc9
P fd74d3d91721ca404537f195fed04c9edef20bf2
R acef59cbf66888f83c956993bdbbc818
P d869eddaf208c4bf03f6bd1848f510392f9dba49
R 13af348b1bf68a7c36428c6258d6aba5
U drh
Z 60d0d33f11d050cbca81ed7def74feb5
Z 7bbfe622f493f625120e8c012f0943a5

View File

@@ -1 +1 @@
d869eddaf208c4bf03f6bd1848f510392f9dba49
62225b4a4c4bfe1820ef54cb202edf2cd866429f

View File

@@ -257,10 +257,11 @@ struct WhereCost {
#define WHERE_TOP_LIMIT 0x00100000 /* x<EXPR or x<=EXPR constraint */
#define WHERE_BTM_LIMIT 0x00200000 /* x>EXPR or x>=EXPR constraint */
#define WHERE_BOTH_LIMIT 0x00300000 /* Both x>EXPR and x<EXPR */
#define WHERE_IDX_ONLY 0x00800000 /* Use index only - omit table */
#define WHERE_ORDERBY 0x01000000 /* Output will appear in correct order */
#define WHERE_REVERSE 0x02000000 /* Scan in reverse order */
#define WHERE_UNIQUE 0x04000000 /* Selects no more than one row */
#define WHERE_IDX_ONLY 0x00400000 /* Use index only - omit table */
#define WHERE_ORDERBY 0x00800000 /* Output will appear in correct order */
#define WHERE_REVERSE 0x01000000 /* Scan in reverse order */
#define WHERE_UNIQUE 0x02000000 /* Selects no more than one row */
#define WHERE_ALL_UNIQUE 0x04000000 /* This and all prior have one row */
#define WHERE_VIRTUALTABLE 0x08000000 /* Use virtual-table processing */
#define WHERE_MULTI_OR 0x10000000 /* OR using multiple indices */
#define WHERE_TEMP_INDEX 0x20000000 /* Uses an ephemeral index */
@@ -1606,166 +1607,6 @@ static int isDistinctRedundant(
return 0;
}
/*
** This routine decides if pIdx can be used to satisfy the ORDER BY
** clause, either in whole or in part. The return value is the
** cumulative number of terms in the ORDER BY clause that are satisfied
** by the index pIdx and other indices in outer loops.
**
** The table being queried has a cursor number of "base". pIdx is the
** index that is postulated for use to access the table.
**
** nEqCol is the number of columns of pIdx that are used as equality
** constraints and where the other side of the == is an ordered column
** or constant. An "order column" in the previous sentence means a column
** in table from an outer loop whose values will always appear in the
** correct order due to othre index, or because the outer loop generates
** a unique result. Any of the first nEqCol columns of pIdx may be missing
** from the ORDER BY clause and the match can still be a success.
**
** The *pbRev value is set to 0 order 1 depending on whether or not
** pIdx should be run in the forward order or in reverse order.
*/
static int isSortingIndex(
WhereBestIdx *p, /* Best index search context */
Index *pIdx, /* The index we are testing */
int base, /* Cursor number for the table to be sorted */
int nEqCol, /* Number of index columns with ordered == constraints */
int wsFlags, /* Index usages flags */
int bOuterRev, /* True if outer loops scan in reverse order */
int *pbRev /* Set to 1 for reverse-order scan of pIdx */
){
int i; /* Number of pIdx terms used */
int j; /* Number of ORDER BY terms satisfied */
int sortOrder = 0; /* XOR of index and ORDER BY sort direction */
int nTerm; /* Number of ORDER BY terms */
struct ExprList_item *pTerm; /* A term of the ORDER BY clause */
ExprList *pOrderBy; /* The ORDER BY clause */
Parse *pParse = p->pParse; /* Parser context */
sqlite3 *db = pParse->db; /* Database connection */
int nPriorSat; /* ORDER BY terms satisfied by outer loops */
int seenRowid = 0; /* True if an ORDER BY rowid term is seen */
int nEqOneRow; /* Idx columns that ref unique values */
if( p->i==0 ){
nPriorSat = 0;
nEqOneRow = nEqCol;
}else{
if( OptimizationDisabled(db, SQLITE_OrderByIdxJoin) ) return 0;
nPriorSat = p->aLevel[p->i-1].plan.nOBSat;
sortOrder = bOuterRev;
nEqOneRow = 0;
}
if( p->i>0 && nEqCol==0 /*&& !allOuterLoopsUnique(p)*/ ) return nPriorSat;
pOrderBy = p->pOrderBy;
if( !pOrderBy ) return nPriorSat;
if( wsFlags & WHERE_COLUMN_IN ) return nPriorSat;
if( pIdx->bUnordered ) return nPriorSat;
nTerm = pOrderBy->nExpr;
assert( nTerm>0 );
/* Argument pIdx must either point to a 'real' named index structure,
** or an index structure allocated on the stack by bestBtreeIndex() to
** represent the rowid index that is part of every table. */
assert( pIdx->zName || (pIdx->nColumn==1 && pIdx->aiColumn[0]==-1) );
/* Match terms of the ORDER BY clause against columns of
** the index.
**
** Note that indices have pIdx->nColumn regular columns plus
** one additional column containing the rowid. The rowid column
** of the index is also allowed to match against the ORDER BY
** clause.
*/
for(i=0,j=nPriorSat,pTerm=&pOrderBy->a[j]; j<nTerm && i<=pIdx->nColumn; i++){
Expr *pExpr; /* The expression of the ORDER BY pTerm */
CollSeq *pColl; /* The collating sequence of pExpr */
int termSortOrder; /* Sort order for this term */
int iColumn; /* The i-th column of the index. -1 for rowid */
int iSortOrder; /* 1 for DESC, 0 for ASC on the i-th index term */
const char *zColl; /* Name of the collating sequence for i-th index term */
pExpr = pTerm->pExpr;
if( pExpr->op!=TK_COLUMN || pExpr->iTable!=base ){
/* Can not use an index sort on anything that is not a column in the
** left-most table of the FROM clause */
break;
}
pColl = sqlite3ExprCollSeq(pParse, pExpr);
if( !pColl ){
pColl = db->pDfltColl;
}
if( pIdx->zName && i<pIdx->nColumn ){
iColumn = pIdx->aiColumn[i];
if( iColumn==pIdx->pTable->iPKey ){
iColumn = -1;
}
iSortOrder = pIdx->aSortOrder[i];
zColl = pIdx->azColl[i];
}else{
iColumn = -1;
iSortOrder = 0;
zColl = pColl->zName;
}
if( pExpr->iColumn!=iColumn || sqlite3StrICmp(pColl->zName, zColl) ){
/* Term j of the ORDER BY clause does not match column i of the index */
if( i<nEqCol ){
/* If an index column that is constrained by == fails to match an
** ORDER BY term, that is OK. Just ignore that column of the index
*/
continue;
}else if( i==pIdx->nColumn ){
/* Index column i is the rowid. All other terms match. */
break;
}else{
/* If an index column fails to match and is not constrained by ==
** then the index cannot satisfy the ORDER BY constraint.
*/
return nPriorSat;
}
}
assert( pIdx->aSortOrder!=0 || iColumn==-1 );
assert( pTerm->sortOrder==0 || pTerm->sortOrder==1 );
assert( iSortOrder==0 || iSortOrder==1 );
termSortOrder = iSortOrder ^ pTerm->sortOrder;
if( i>nEqOneRow ){
if( termSortOrder!=sortOrder ){
/* Indices can only be used if all ORDER BY terms past the
** equality constraints are all either DESC or ASC. */
break;
}
}else{
sortOrder = termSortOrder;
}
j++;
pTerm++;
if( iColumn<0 ){
seenRowid = 1;
break;
}
}
*pbRev = sortOrder;
/* If there was an "ORDER BY rowid" term that matched, or it is only
** possible for a single row from this table to match, then skip over
** any additional ORDER BY terms dealing with this table.
*/
if( seenRowid ||
( (wsFlags & WHERE_COLUMN_NULL)==0
&& i>=pIdx->nColumn
&& indexIsUniqueNotNull(pIdx, nEqCol)
)
){
/* Advance j over additional ORDER BY terms associated with base */
WhereMaskSet *pMS = p->pWC->pMaskSet;
Bitmask m = ~getMask(pMS, base);
while( j<nTerm && (exprTableUsage(pMS, pOrderBy->a[j].pExpr)&m)==0 ){
j++;
}
}
return j;
}
/*
** Prepare a crude estimate of the logarithm of the input value.
** The results need not be exact. This is only used for estimating
@@ -2884,6 +2725,9 @@ static int isOrderedColumn(WhereBestIdx *p, int iTab, int iCol, int *pbRev){
u8 sortOrder;
for(i=p->i-1; i>=0; i--, pLevel--){
if( pLevel->iTabCur!=iTab ) continue;
if( (pLevel->plan.wsFlags & WHERE_ALL_UNIQUE)!=0 ){
return 1;
}
if( (pLevel->plan.wsFlags & WHERE_INDEXED)!=0 ){
pIdx = pLevel->plan.u.pIdx;
if( iCol<0 ){
@@ -2926,9 +2770,6 @@ static int isOrderedTerm(WhereBestIdx *p, WhereTerm *pTerm, int *pbRev){
assert( pExpr->op==TK_EQ );
assert( pExpr->pLeft!=0 && pExpr->pLeft->op==TK_COLUMN );
assert( pExpr->pRight!=0 );
if( p->i==0 ){
return 1; /* All == are ordered in the outer loop */
}
if( pTerm->prereqRight==0 ){
return 1; /* RHS of the == is a constant */
}
@@ -2942,6 +2783,168 @@ static int isOrderedTerm(WhereBestIdx *p, WhereTerm *pTerm, int *pbRev){
return 0;
}
/*
** This routine decides if pIdx can be used to satisfy the ORDER BY
** clause, either in whole or in part. The return value is the
** cumulative number of terms in the ORDER BY clause that are satisfied
** by the index pIdx and other indices in outer loops.
**
** The table being queried has a cursor number of "base". pIdx is the
** index that is postulated for use to access the table.
**
** nEqCol is the number of columns of pIdx that are used as equality
** constraints and where the other side of the == is an ordered column
** or constant. An "order column" in the previous sentence means a column
** in table from an outer loop whose values will always appear in the
** correct order due to othre index, or because the outer loop generates
** a unique result. Any of the first nEqCol columns of pIdx may be missing
** from the ORDER BY clause and the match can still be a success.
**
** The *pbRev value is set to 0 order 1 depending on whether or not
** pIdx should be run in the forward order or in reverse order.
*/
static int isSortingIndex(
WhereBestIdx *p, /* Best index search context */
Index *pIdx, /* The index we are testing */
int base, /* Cursor number for the table to be sorted */
int nEqCol, /* Number of index columns with ordered == constraints */
int wsFlags, /* Index usages flags */
int bOuterRev, /* True if outer loops scan in reverse order */
int *pbRev /* Set to 1 for reverse-order scan of pIdx */
){
int i; /* Number of pIdx terms used */
int j; /* Number of ORDER BY terms satisfied */
int sortOrder = 0; /* XOR of index and ORDER BY sort direction */
int nTerm; /* Number of ORDER BY terms */
struct ExprList_item *pTerm; /* A term of the ORDER BY clause */
ExprList *pOrderBy; /* The ORDER BY clause */
Parse *pParse = p->pParse; /* Parser context */
sqlite3 *db = pParse->db; /* Database connection */
int nPriorSat; /* ORDER BY terms satisfied by outer loops */
int seenRowid = 0; /* True if an ORDER BY rowid term is seen */
int nEqOneRow; /* Idx columns that ref unique values */
if( p->i==0 ){
nPriorSat = 0;
}else{
nPriorSat = p->aLevel[p->i-1].plan.nOBSat;
if( OptimizationDisabled(db, SQLITE_OrderByIdxJoin) ) return nPriorSat;
}
if( p->i==0 || (p->aLevel[p->i-1].plan.wsFlags & WHERE_ALL_UNIQUE)!=0 ){
nEqOneRow = nEqCol;
}else{
if( nEqCol==0 ) return nPriorSat;
sortOrder = bOuterRev;
nEqOneRow = 0;
}
pOrderBy = p->pOrderBy;
assert( pOrderBy!=0 );
if( wsFlags & WHERE_COLUMN_IN ) return nPriorSat;
if( pIdx->bUnordered ) return nPriorSat;
nTerm = pOrderBy->nExpr;
assert( nTerm>0 );
/* Argument pIdx must either point to a 'real' named index structure,
** or an index structure allocated on the stack by bestBtreeIndex() to
** represent the rowid index that is part of every table. */
assert( pIdx->zName || (pIdx->nColumn==1 && pIdx->aiColumn[0]==-1) );
/* Match terms of the ORDER BY clause against columns of
** the index.
**
** Note that indices have pIdx->nColumn regular columns plus
** one additional column containing the rowid. The rowid column
** of the index is also allowed to match against the ORDER BY
** clause.
*/
for(i=0,j=nPriorSat,pTerm=&pOrderBy->a[j]; j<nTerm && i<=pIdx->nColumn; i++){
Expr *pExpr; /* The expression of the ORDER BY pTerm */
CollSeq *pColl; /* The collating sequence of pExpr */
int termSortOrder; /* Sort order for this term */
int iColumn; /* The i-th column of the index. -1 for rowid */
int iSortOrder; /* 1 for DESC, 0 for ASC on the i-th index term */
const char *zColl; /* Name of the collating sequence for i-th index term */
pExpr = pTerm->pExpr;
if( pExpr->op!=TK_COLUMN || pExpr->iTable!=base ){
/* Can not use an index sort on anything that is not a column in the
** left-most table of the FROM clause */
break;
}
pColl = sqlite3ExprCollSeq(pParse, pExpr);
if( !pColl ){
pColl = db->pDfltColl;
}
if( pIdx->zName && i<pIdx->nColumn ){
iColumn = pIdx->aiColumn[i];
if( iColumn==pIdx->pTable->iPKey ){
iColumn = -1;
}
iSortOrder = pIdx->aSortOrder[i];
zColl = pIdx->azColl[i];
}else{
iColumn = -1;
iSortOrder = 0;
zColl = pColl->zName;
}
if( pExpr->iColumn!=iColumn || sqlite3StrICmp(pColl->zName, zColl) ){
/* Term j of the ORDER BY clause does not match column i of the index */
if( i<nEqCol ){
/* If an index column that is constrained by == fails to match an
** ORDER BY term, that is OK. Just ignore that column of the index
*/
continue;
}else if( i==pIdx->nColumn ){
/* Index column i is the rowid. All other terms match. */
break;
}else{
/* If an index column fails to match and is not constrained by ==
** then the index cannot satisfy the ORDER BY constraint.
*/
return nPriorSat;
}
}
assert( pIdx->aSortOrder!=0 || iColumn==-1 );
assert( pTerm->sortOrder==0 || pTerm->sortOrder==1 );
assert( iSortOrder==0 || iSortOrder==1 );
termSortOrder = iSortOrder ^ pTerm->sortOrder;
if( i>nEqOneRow ){
if( termSortOrder!=sortOrder ){
/* Indices can only be used if all ORDER BY terms past the
** equality constraints are all either DESC or ASC. */
break;
}
}else{
sortOrder = termSortOrder;
}
j++;
pTerm++;
if( iColumn<0 ){
seenRowid = 1;
break;
}
}
*pbRev = sortOrder;
/* If there was an "ORDER BY rowid" term that matched, or it is only
** possible for a single row from this table to match, then skip over
** any additional ORDER BY terms dealing with this table.
*/
if( seenRowid ||
( (wsFlags & WHERE_COLUMN_NULL)==0
&& i>=pIdx->nColumn
&& indexIsUniqueNotNull(pIdx, nEqCol)
)
){
/* Advance j over additional ORDER BY terms associated with base */
WhereMaskSet *pMS = p->pWC->pMaskSet;
Bitmask m = ~getMask(pMS, base);
while( j<nTerm && (exprTableUsage(pMS, pOrderBy->a[j].pExpr)&m)==0 ){
j++;
}
}
return j;
}
/*
** Find the best query plan for accessing a particular table. Write the
@@ -3177,6 +3180,9 @@ static void bestBtreeIndex(WhereBestIdx *p){
testcase( wsFlags & WHERE_COLUMN_NULL );
if( (wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_NULL))==0 ){
wsFlags |= WHERE_UNIQUE;
if( p->i==0 || (p->aLevel[p->i-1].plan.wsFlags & WHERE_ALL_UNIQUE)!=0 ){
wsFlags |= WHERE_ALL_UNIQUE;
}
}
}else if( pProbe->bUnordered==0 ){
int j = (nEq==pProbe->nColumn ? -1 : pProbe->aiColumn[nEq]);

71
test/orderby2.test Normal file
View File

@@ -0,0 +1,71 @@
# 2012 Sept 27
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The
# focus of this file is testing that the optimizations that disable
# ORDER BY clauses when the natural order of a query is correct.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix orderby2
# Generate test data for a join. Verify that the join gets the
# correct answer.
#
do_test 1.0 {
db eval {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
INSERT INTO t1 VALUES(1,11), (2,22);
CREATE TABLE t2(d, e, UNIQUE(d,e));
INSERT INTO t2 VALUES(10, 'ten'), (11,'eleven'), (12,'twelve'),
(11, 'oneteen');
}
} {}
do_test 1.1a {
db eval {
SELECT e FROM t1, t2 WHERE a=1 AND d=b ORDER BY d, e;
}
} {eleven oneteen}
do_test 1.1b {
db eval {
EXPLAIN QUERY PLAN
SELECT e FROM t1, t2 WHERE a=1 AND d=b ORDER BY d, e;
}
} {~/ORDER BY/}
do_test 1.2a {
db eval {
SELECT e FROM t1, t2 WHERE a=1 AND d=b ORDER BY e;
}
} {eleven oneteen}
do_test 1.2b {
db eval {
EXPLAIN QUERY PLAN
SELECT e FROM t1, t2 WHERE a=1 AND d=b ORDER BY e;
}
} {~/ORDER BY/}
do_test 1.3a {
db eval {
SELECT e, b FROM t1, t2 WHERE a=1 ORDER BY d, e;
}
} {ten 11 eleven 11 oneteen 11 twelve 11}
do_test 1.3b {
db eval {
EXPLAIN QUERY PLAN
SELECT e, b FROM t1, t2 WHERE a=1 ORDER BY d, e;
}
} {~/ORDER BY/}
finish_test