Fix a performance regression (relative to version 3.6.23.1) caused by the

query planner taking into account non-indexable WHERE clause terms to select
the outermost join loops when it should be selecting tables for the outermost
loop that do not benefit from being in an inner loop.

FossilOrigin-Name: ece641eb8951c6314cedbdb3243f91cb199c3239
This commit is contained in:
drh 2010-10-04 23:55:50 +00:00
parent b4eb82f6cf
commit 547caad4c4
4 changed files with 126 additions and 35 deletions

View File

@ -1,5 +1,8 @@
C Fix\sa\scouple\sof\stest\sscript\sproblems.
D 2010-10-04T16:06:12
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
C Fix\sa\sperformance\sregression\s(relative\sto\sversion\s3.6.23.1)\scaused\sby\sthe\nquery\splanner\staking\sinto\saccount\snon-indexable\sWHERE\sclause\sterms\sto\sselect\nthe\soutermost\sjoin\sloops\swhen\sit\sshould\sbe\sselecting\stables\sfor\sthe\soutermost\nloop\sthat\sdo\snot\sbenefit\sfrom\sbeing\sin\san\sinner\sloop.
D 2010-10-04T23:55:51
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in c599a15d268b1db2aeadea19df2adc3bf2eb6bee
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@ -237,7 +240,7 @@ F src/vtab.c 6c90e3e65b2f026fc54703a8f3c917155f419d87
F src/wal.c 7081f148cb52b0cf2280e6384196402dc58130a3
F src/wal.h 96669b645e27cd5a111ba59f0cae7743a207bc3c
F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f
F src/where.c 34c733f9e8ca5e8f611958422e7cc236bc9cf739
F src/where.c 204cdfb66eb82ee17a8fc0a9b12c1ab402755cbb
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87
F test/all.test 6745008c144bd2956d58864d21f7b304689c1cce
@ -828,7 +831,7 @@ F test/walslow.test d21625e2e99e11c032ce949e8a94661576548933
F test/walthread.test a25a393c068a2b42b44333fa3fdaae9072f1617c
F test/where.test de337a3fe0a459ec7c93db16a519657a90552330
F test/where2.test 43d4becaf5a5df854e6c21d624a1cb84c6904554
F test/where3.test 3bf8006d441b66a57bee02bb420423f84eb8fde3
F test/where3.test 3a72db38e8804b210e9f72001ea16830fea74b4b
F test/where4.test e9b9e2f2f98f00379e6031db6a6fca29bae782a2
F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2
F test/where6.test 5da5a98cec820d488e82708301b96cb8c18a258b
@ -872,7 +875,14 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
P 8ad88ee0c1145eb9f92267c31d7d787739718716
R 3062623d0ad76429d2e484f95cd0fcaf
U dan
Z 126f912f656c0260d2a9bacbd10d60fe
P dd106901407a4d98644dd614e16e9fdc10cd7423
R cdc7b980473125bc516a169d40b86103
U drh
Z 6ab09c962683b0c7fab62719b77e8d38
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
iD8DBQFMqmmLoxKgR168RlERAp+nAJoD7LopkWFS5QUzO6hLt0RvySH/QACcDbon
33RkCFR9+ouK2CTDhFfslEU=
=Nqov
-----END PGP SIGNATURE-----

View File

@ -1 +1 @@
dd106901407a4d98644dd614e16e9fdc10cd7423
ece641eb8951c6314cedbdb3243f91cb199c3239

View File

@ -1556,7 +1556,8 @@ static void TRACE_IDX_OUTPUTS(sqlite3_index_info *p){
** Required because bestIndex() is called by bestOrClauseIndex()
*/
static void bestIndex(
Parse*, WhereClause*, struct SrcList_item*, Bitmask, ExprList*, WhereCost*);
Parse*, WhereClause*, struct SrcList_item*,
Bitmask, Bitmask, ExprList*, WhereCost*);
/*
** This routine attempts to find an scanning strategy that can be used
@ -1569,7 +1570,8 @@ static void bestOrClauseIndex(
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 */
Bitmask notReady, /* Mask of cursors not available for indexing */
Bitmask notValid, /* Cursors not available for any purpose */
ExprList *pOrderBy, /* The ORDER BY clause */
WhereCost *pCost /* Lowest cost query plan */
){
@ -1605,7 +1607,7 @@ static void bestOrClauseIndex(
));
if( pOrTerm->eOperator==WO_AND ){
WhereClause *pAndWC = &pOrTerm->u.pAndInfo->wc;
bestIndex(pParse, pAndWC, pSrc, notReady, 0, &sTermCost);
bestIndex(pParse, pAndWC, pSrc, notReady, notValid, 0, &sTermCost);
}else if( pOrTerm->leftCursor==iCur ){
WhereClause tempWC;
tempWC.pParse = pWC->pParse;
@ -1613,7 +1615,7 @@ static void bestOrClauseIndex(
tempWC.op = TK_AND;
tempWC.a = pOrTerm;
tempWC.nTerm = 1;
bestIndex(pParse, &tempWC, pSrc, notReady, 0, &sTermCost);
bestIndex(pParse, &tempWC, pSrc, notReady, notValid, 0, &sTermCost);
}else{
continue;
}
@ -2060,7 +2062,8 @@ static void bestVirtualIndex(
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 */
Bitmask notReady, /* Mask of cursors not available for index */
Bitmask notValid, /* Cursors not valid for any purpose */
ExprList *pOrderBy, /* The order by clause */
WhereCost *pCost, /* Lowest cost query plan */
sqlite3_index_info **ppIdxInfo /* Index information passed to xBestIndex */
@ -2190,7 +2193,7 @@ static void bestVirtualIndex(
/* Try to find a more efficient access pattern by using multiple indexes
** to optimize an OR expression within the WHERE clause.
*/
bestOrClauseIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost);
bestOrClauseIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost);
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */
@ -2471,7 +2474,8 @@ 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 */
Bitmask notReady, /* Mask of cursors not available for indexing */
Bitmask notValid, /* Cursors not available for any purpose */
ExprList *pOrderBy, /* The ORDER BY clause */
WhereCost *pCost /* Lowest cost query plan */
){
@ -2733,16 +2737,16 @@ static void bestBtreeIndex(
** with this step if we already know this index will not be chosen.
** Also, never reduce the output row count below 2 using this step.
**
** Do not reduce the output row count if pSrc is the only table that
** is notReady; if notReady is a power of two. This will be the case
** when the main sqlite3WhereBegin() loop is scanning for a table with
** and "optimal" index, and on such a scan the output row count
** reduction is not valid because it does not update the "pCost->used"
** bitmap. The notReady bitmap will also be a power of two when we
** are scanning for the last table in a 64-way join. We are willing
** to bypass this optimization in that corner case.
** It is critical that the notValid mask be used here instead of
** the notReady mask. When computing an "optimal" index, the notReady
** mask will only have one bit set - the bit for the current table.
** The notValid mask, on the other hand, always has all bits set for
** tables that are not in outer loops. If notReady is used here instead
** of notValid, then a optimal index that depends on inner joins loops
** might be selected even when there exists an optimal index that has
** no such dependency.
*/
if( nRow>2 && cost<=pCost->rCost && (notReady & (notReady-1))!=0 ){
if( nRow>2 && cost<=pCost->rCost ){
int k; /* Loop counter */
int nSkipEq = nEq; /* Number of == constraints to skip */
int nSkipRange = nBound; /* Number of < constraints to skip */
@ -2751,7 +2755,7 @@ static void bestBtreeIndex(
thisTab = getMask(pWC->pMaskSet, iCur);
for(pTerm=pWC->a, k=pWC->nTerm; nRow>2 && k; k--, pTerm++){
if( pTerm->wtFlags & TERM_VIRTUAL ) continue;
if( (pTerm->prereqAll & notReady)!=thisTab ) continue;
if( (pTerm->prereqAll & notValid)!=thisTab ) continue;
if( pTerm->eOperator & (WO_EQ|WO_IN|WO_ISNULL) ){
if( nSkipEq ){
/* Ignore the first nEq equality matches since the index
@ -2833,7 +2837,7 @@ static void bestBtreeIndex(
pCost->plan.u.pIdx ? pCost->plan.u.pIdx->zName : "ipk")
));
bestOrClauseIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost);
bestOrClauseIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost);
bestAutomaticIndex(pParse, pWC, pSrc, notReady, pCost);
pCost->plan.wsFlags |= eqTermMask;
}
@ -2848,14 +2852,15 @@ static void bestIndex(
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 */
Bitmask notReady, /* Mask of cursors not available for indexing */
Bitmask notValid, /* Cursors not available for any purpose */
ExprList *pOrderBy, /* The ORDER BY clause */
WhereCost *pCost /* Lowest cost query plan */
){
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pSrc->pTab) ){
sqlite3_index_info *p = 0;
bestVirtualIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost, &p);
bestVirtualIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost,&p);
if( p->needToFreeIdxStr ){
sqlite3_free(p->idxStr);
}
@ -2863,7 +2868,7 @@ static void bestIndex(
}else
#endif
{
bestBtreeIndex(pParse, pWC, pSrc, notReady, pOrderBy, pCost);
bestBtreeIndex(pParse, pWC, pSrc, notReady, notValid, pOrderBy, pCost);
}
}
@ -4079,9 +4084,15 @@ WhereInfo *sqlite3WhereBegin(
** other FROM clause terms that are notReady. If no notReady terms are
** used then the "optimal" query plan works.
**
** Note that the WhereCost.nRow parameter for an optimal scan might
** not be as small as it would be if the table really were the innermost
** join. The nRow value can be reduced by WHERE clause constraints
** that do not use indices. But this nRow reduction only happens if the
** table really is the innermost join.
**
** The second loop iteration is only performed if no optimal scan
** strategies were found by the first loop. This 2nd iteration is used to
** search for the lowest cost scan overall.
** strategies were found by the first iteration. This second iteration
** is used to search for the lowest cost scan overall.
**
** Previous versions of SQLite performed only the second iteration -
** the next outermost loop was always that with the lowest overall
@ -4101,7 +4112,7 @@ WhereInfo *sqlite3WhereBegin(
*/
nUnconstrained = 0;
notIndexed = 0;
for(isOptimal=(iFrom<nTabList-1); isOptimal>=0; isOptimal--){
for(isOptimal=(iFrom<nTabList-1); isOptimal>=0 && bestJ<0; isOptimal--){
Bitmask mask; /* Mask of tables not yet ready */
for(j=iFrom, pTabItem=&pTabList->a[j]; j<nTabList; j++, pTabItem++){
int doNotReorder; /* True if this table should not be reordered */
@ -4123,11 +4134,13 @@ WhereInfo *sqlite3WhereBegin(
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pTabItem->pTab) ){
sqlite3_index_info **pp = &pWInfo->a[j].pIdxInfo;
bestVirtualIndex(pParse, pWC, pTabItem, mask, pOrderBy, &sCost, pp);
bestVirtualIndex(pParse, pWC, pTabItem, mask, notReady, pOrderBy,
&sCost, pp);
}else
#endif
{
bestBtreeIndex(pParse, pWC, pTabItem, mask, pOrderBy, &sCost);
bestBtreeIndex(pParse, pWC, pTabItem, mask, notReady, pOrderBy,
&sCost);
}
assert( isOptimal || (sCost.used&notReady)==0 );

View File

@ -261,4 +261,72 @@ do_test where3-4.2 {
}
} {0 0 {TABLE t400} 1 1 {TABLE t401} 2 2 {TABLE t402}}
# Verify that a performance regression encountered by firefox
# has been fixed.
#
do_test where3-5.0 {
execsql {
CREATE TABLE aaa (id INTEGER PRIMARY KEY, type INTEGER,
fk INTEGER DEFAULT NULL, parent INTEGER,
position INTEGER, title LONGVARCHAR,
keyword_id INTEGER, folder_type TEXT,
dateAdded INTEGER, lastModified INTEGER);
CREATE INDEX aaa_111 ON aaa (fk, type);
CREATE INDEX aaa_222 ON aaa (parent, position);
CREATE INDEX aaa_333 ON aaa (fk, lastModified);
CREATE TABLE bbb (id INTEGER PRIMARY KEY, type INTEGER,
fk INTEGER DEFAULT NULL, parent INTEGER,
position INTEGER, title LONGVARCHAR,
keyword_id INTEGER, folder_type TEXT,
dateAdded INTEGER, lastModified INTEGER);
CREATE INDEX bbb_111 ON bbb (fk, type);
CREATE INDEX bbb_222 ON bbb (parent, position);
CREATE INDEX bbb_333 ON bbb (fk, lastModified);
}
execsql {
EXPLAIN QUERY PLAN
SELECT bbb.title AS tag_title
FROM aaa JOIN bbb ON bbb.id = aaa.parent
WHERE aaa.fk = 'constant'
AND LENGTH(bbb.title) > 0
AND bbb.parent = 4
ORDER BY bbb.title COLLATE NOCASE ASC;
}
} {0 0 {TABLE aaa WITH INDEX aaa_333} 1 1 {TABLE bbb USING PRIMARY KEY}}
do_test where3-5.1 {
execsql {
EXPLAIN QUERY PLAN
SELECT bbb.title AS tag_title
FROM aaa JOIN aaa AS bbb ON bbb.id = aaa.parent
WHERE aaa.fk = 'constant'
AND LENGTH(bbb.title) > 0
AND bbb.parent = 4
ORDER BY bbb.title COLLATE NOCASE ASC;
}
} {0 0 {TABLE aaa WITH INDEX aaa_333} 1 1 {TABLE aaa AS bbb USING PRIMARY KEY}}
do_test where3-5.2 {
execsql {
EXPLAIN QUERY PLAN
SELECT bbb.title AS tag_title
FROM bbb JOIN aaa ON bbb.id = aaa.parent
WHERE aaa.fk = 'constant'
AND LENGTH(bbb.title) > 0
AND bbb.parent = 4
ORDER BY bbb.title COLLATE NOCASE ASC;
}
} {0 1 {TABLE aaa WITH INDEX aaa_333} 1 0 {TABLE bbb USING PRIMARY KEY}}
do_test where3-5.3 {
execsql {
EXPLAIN QUERY PLAN
SELECT bbb.title AS tag_title
FROM aaa AS bbb JOIN aaa ON bbb.id = aaa.parent
WHERE aaa.fk = 'constant'
AND LENGTH(bbb.title) > 0
AND bbb.parent = 4
ORDER BY bbb.title COLLATE NOCASE ASC;
}
} {0 1 {TABLE aaa WITH INDEX aaa_333} 1 0 {TABLE aaa AS bbb USING PRIMARY KEY}}
finish_test