1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-10 01:02:56 +03:00

Avoid unnecessary cursor seeks during upsert processing.

FossilOrigin-Name: 7c4b6d5475092a3e205f01a6972366e27a404568e8e7ba327f2feefac2ce2c7c
This commit is contained in:
drh
2018-04-20 15:56:24 +00:00
parent a46838cb13
commit fb2213e1ff
4 changed files with 162 additions and 155 deletions

View File

@@ -1,5 +1,5 @@
C Add\stest\scases\sfor\sUPSERT.\sAnd\sa\sfix\sfor\sa\s"REPLACE\sINTO\s...\sON\sCONFLICT"\nstatement\swhere\sthe\snew\srow\sconflicts\swith\sboth\sthe\sIPK\sand\sthe\sON\sCONFLICT\nindexes. C Avoid\sunnecessary\scursor\sseeks\sduring\supsert\sprocessing.
D 2018-04-20T15:34:08.067 D 2018-04-20T15:56:24.816
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F Makefile.in 5ce9343cba9c189046f1afe6d2bcc1f68079439febc05267b98aec6ecc752439 F Makefile.in 5ce9343cba9c189046f1afe6d2bcc1f68079439febc05267b98aec6ecc752439
@@ -557,8 +557,8 @@ F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
F src/tokenize.c 5b0c661a85f783d35b9883830736eeb63be4aefc4f6b7d9cd081d48782c041e2 F src/tokenize.c 5b0c661a85f783d35b9883830736eeb63be4aefc4f6b7d9cd081d48782c041e2
F src/treeview.c 14d5d1254702ec96876aa52642cb31548612384134970409fae333b25b39d6bb F src/treeview.c 14d5d1254702ec96876aa52642cb31548612384134970409fae333b25b39d6bb
F src/trigger.c 4ace6d1d5ba9a89822deb287317f33c810440526eafe185c2d8a48c31df1e995 F src/trigger.c 4ace6d1d5ba9a89822deb287317f33c810440526eafe185c2d8a48c31df1e995
F src/update.c 86e6563430a05d9df439cb0253ade0616e3320ef38fa0ed20fb1634e2d9aca0a F src/update.c 2f460e57440cd673bb375b5601029d736f3d604cad2b476e31b1f63b10356bce
F src/upsert.c 71ebb84f330b0d72675c694303a3801633ff4079af9a40569f7e528219484969 F src/upsert.c 4f1d04b8cbae727c066cb2203dcefb6c4a4e3c54ba6345e5ec0b515e48381299
F src/utf.c 810fbfebe12359f10bc2a011520a6e10879ab2a163bcb26c74768eab82ea62a5 F src/utf.c 810fbfebe12359f10bc2a011520a6e10879ab2a163bcb26c74768eab82ea62a5
F src/util.c d9eb0a6c4aae1b00a7369eadd7ca0bbe946cb4c953b6751aa20d357c2f482157 F src/util.c d9eb0a6c4aae1b00a7369eadd7ca0bbe946cb4c953b6751aa20d357c2f482157
F src/vacuum.c 762ee9bbf8733d87d8cd06f58d950e881982e416f8c767334a40ffd341b6bff5 F src/vacuum.c 762ee9bbf8733d87d8cd06f58d950e881982e416f8c767334a40ffd341b6bff5
@@ -1724,7 +1724,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P c37f39d18d41ae5ba6c4561d87cbbf71f3b6896b86cc5cff9cdf046b02dc521a P d8eb9f8d9b61400c7e12f01ef5c233257b03532221f7c7a8386f7ac2db439626
R ae514c5e5a987302d42c4363153aaf33 R 75119827b876db768b1909c51e8a9eca
U dan T *branch * upsert-opt2
Z a0358cccdac92456839b19732d4467cc T *sym-upsert-opt2 *
T -sym-trunk *
U drh
Z 388a812d3bddf28e9c1a7fb5f5cfd19e

View File

@@ -1 +1 @@
d8eb9f8d9b61400c7e12f01ef5c233257b03532221f7c7a8386f7ac2db439626 7c4b6d5475092a3e205f01a6972366e27a404568e8e7ba327f2feefac2ce2c7c

View File

@@ -212,6 +212,7 @@ void sqlite3Update(
pParse->nTab++; pParse->nTab++;
} }
if( pUpsert ){ if( pUpsert ){
/* On an UPSERT, reuse the same cursors already opened by INSERT */
iDataCur = pUpsert->iDataCur; iDataCur = pUpsert->iDataCur;
iIdxCur = pUpsert->iIdxCur; iIdxCur = pUpsert->iIdxCur;
pParse->nTab = iBaseCur; pParse->nTab = iBaseCur;
@@ -389,64 +390,85 @@ void sqlite3Update(
} }
#endif #endif
/* Initialize the count of updated rows */ /* Jump to labelBreak to abandon further processing of this UPDATE */
if( (db->flags&SQLITE_CountRows)!=0 labelBreak = sqlite3VdbeMakeLabel(v);
&& !pParse->pTriggerTab
&& !pParse->nested
&& !pUpsert
){
regRowCount = ++pParse->nMem;
sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
}
if( HasRowid(pTab) ){ if( pUpsert ){
sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid); /* If this is an UPSERT, then all cursors have already been opened by
}else{ ** the outer INSERT and the data cursor should be pointing at the row
assert( pPk!=0 ); ** that is to be updated. So bypass the code that searches for the
nPk = pPk->nKeyCol; ** row(s) to be updated.
iPk = pParse->nMem+1; */
pParse->nMem += nPk; pWInfo = 0;
regKey = ++pParse->nMem; eOnePass = ONEPASS_SINGLE;
iEph = pParse->nTab++; labelContinue = labelBreak;
if( !HasRowid(pTab) ){
sqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1); nPk = pPk->nKeyCol;
addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk); iPk = pParse->nMem+1;
sqlite3VdbeSetP4KeyInfo(pParse, pPk); pParse->nMem += nPk;
} regKey = ++pParse->nMem;
}
/* Begin the database scan. sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL);
** }else{
** Do not consider a single-pass strategy for a multi-row update if /* Not an UPSERT. Normal processing. Begin by
** there are any triggers or foreign keys to process, or rows may ** initialize the count of updated rows */
** be deleted as a result of REPLACE conflict handling. Any of these if( (db->flags&SQLITE_CountRows)!=0
** things might disturb a cursor being used to scan through the table && !pParse->pTriggerTab
** or index, causing a single-pass approach to malfunction. */ && !pParse->nested
flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE; ){
if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){ regRowCount = ++pParse->nMem;
flags |= WHERE_ONEPASS_MULTIROW; sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
}
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur);
if( pWInfo==0 ) goto update_cleanup;
/* A one-pass strategy that might update more than one row may not
** be used if any column of the index used for the scan is being
** updated. Otherwise, if there is an index on "b", statements like
** the following could create an infinite loop:
**
** UPDATE t1 SET b=b+1 WHERE b>?
**
** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI
** strategy that uses an index for which one or more columns are being
** updated. */
eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
if( eOnePass==ONEPASS_MULTI ){
int iCur = aiCurOnePass[1];
if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){
eOnePass = ONEPASS_OFF;
} }
assert( iCur!=iDataCur || !HasRowid(pTab) );
}
if( HasRowid(pTab) ){
sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid);
}else{
assert( pPk!=0 );
nPk = pPk->nKeyCol;
iPk = pParse->nMem+1;
pParse->nMem += nPk;
regKey = ++pParse->nMem;
iEph = pParse->nTab++;
sqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1);
addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk);
sqlite3VdbeSetP4KeyInfo(pParse, pPk);
}
/* Begin the database scan.
**
** Do not consider a single-pass strategy for a multi-row update if
** there are any triggers or foreign keys to process, or rows may
** be deleted as a result of REPLACE conflict handling. Any of these
** things might disturb a cursor being used to scan through the table
** or index, causing a single-pass approach to malfunction. */
flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE;
if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
flags |= WHERE_ONEPASS_MULTIROW;
}
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur);
if( pWInfo==0 ) goto update_cleanup;
/* A one-pass strategy that might update more than one row may not
** be used if any column of the index used for the scan is being
** updated. Otherwise, if there is an index on "b", statements like
** the following could create an infinite loop:
**
** UPDATE t1 SET b=b+1 WHERE b>?
**
** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI
** strategy that uses an index for which one or more columns are being
** updated. */
eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
if( eOnePass==ONEPASS_MULTI ){
int iCur = aiCurOnePass[1];
if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){
eOnePass = ONEPASS_OFF;
}
assert( iCur!=iDataCur || !HasRowid(pTab) );
}
}
if( HasRowid(pTab) ){ if( HasRowid(pTab) ){
/* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF /* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF
** mode, write the rowid into the FIFO. In either of the one-pass modes, ** mode, write the rowid into the FIFO. In either of the one-pass modes,
@@ -466,7 +488,7 @@ void sqlite3Update(
sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur,pPk->aiColumn[i],iPk+i); sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur,pPk->aiColumn[i],iPk+i);
} }
if( eOnePass ){ if( eOnePass ){
sqlite3VdbeChangeToNoop(v, addrOpen); if( addrOpen ) sqlite3VdbeChangeToNoop(v, addrOpen);
nKey = nPk; nKey = nPk;
regKey = iPk; regKey = iPk;
}else{ }else{
@@ -476,55 +498,56 @@ void sqlite3Update(
} }
} }
if( eOnePass!=ONEPASS_MULTI ){ if( pUpsert==0 ){
sqlite3WhereEnd(pWInfo); if( eOnePass!=ONEPASS_MULTI ){
} sqlite3WhereEnd(pWInfo);
}
labelBreak = sqlite3VdbeMakeLabel(v);
if( !isView && pUpsert==0 ){ if( !isView ){
int addrOnce = 0; int addrOnce = 0;
/* Open every index that needs updating. */ /* Open every index that needs updating. */
if( eOnePass!=ONEPASS_OFF ){
if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0;
if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0;
}
if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){
addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
}
sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur,
aToOpen, 0, 0);
if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce);
}
/* Top of the update loop */
if( eOnePass!=ONEPASS_OFF ){ if( eOnePass!=ONEPASS_OFF ){
if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0; if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){
if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0; assert( pPk );
} sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey,nKey);
VdbeCoverageNeverTaken(v);
if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){ }
addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); if( eOnePass==ONEPASS_SINGLE ){
} labelContinue = labelBreak;
sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, aToOpen, }else{
0, 0); labelContinue = sqlite3VdbeMakeLabel(v);
if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); }
} sqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak);
VdbeCoverageIf(v, pPk==0);
/* Top of the update loop */ VdbeCoverageIf(v, pPk!=0);
if( eOnePass!=ONEPASS_OFF ){ }else if( pPk ){
if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){
assert( pPk );
sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey, nKey);
VdbeCoverageNeverTaken(v);
}
if( eOnePass==ONEPASS_SINGLE ){
labelContinue = labelBreak;
}else{
labelContinue = sqlite3VdbeMakeLabel(v); labelContinue = sqlite3VdbeMakeLabel(v);
sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v);
addrTop = sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey);
sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0);
VdbeCoverage(v);
}else{
labelContinue = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet,labelBreak,
regOldRowid);
VdbeCoverage(v);
sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
VdbeCoverage(v);
} }
sqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak);
VdbeCoverageIf(v, pPk==0);
VdbeCoverageIf(v, pPk!=0);
}else if( pPk ){
labelContinue = sqlite3VdbeMakeLabel(v);
sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v);
addrTop = sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey);
sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0);
VdbeCoverage(v);
}else{
labelContinue = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet, labelBreak,
regOldRowid);
VdbeCoverage(v);
sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
VdbeCoverage(v);
} }
/* If the rowid value will change, set register regNewRowid to /* If the rowid value will change, set register regNewRowid to

View File

@@ -203,62 +203,43 @@ void sqlite3UpsertDoUpdate(
){ ){
Vdbe *v = pParse->pVdbe; Vdbe *v = pParse->pVdbe;
sqlite3 *db = pParse->db; sqlite3 *db = pParse->db;
int regKey; /* Register(s) containing the key */
Expr *pWhere; /* Where clause for the UPDATE */
Expr *pE1, *pE2;
SrcList *pSrc; /* FROM clause for the UPDATE */ SrcList *pSrc; /* FROM clause for the UPDATE */
int iDataCur = pUpsert->iDataCur;
assert( v!=0 ); assert( v!=0 );
VdbeNoopComment((v, "Begin DO UPDATE of UPSERT")); VdbeNoopComment((v, "Begin DO UPDATE of UPSERT"));
pWhere = sqlite3ExprDup(db, pUpsert->pUpsertWhere, 0); if( pIdx && iCur!=iDataCur ){
if( pIdx==0 || HasRowid(pTab) ){ if( HasRowid(pTab) ){
/* We are dealing with an IPK */ int regRowid = sqlite3GetTempReg(pParse);
regKey = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_IdxRowid, iCur, regRowid);
if( pIdx ){ sqlite3VdbeAddOp3(v, OP_SeekRowid, iDataCur, 0, regRowid);
sqlite3VdbeAddOp2(v, OP_IdxRowid, iCur, regKey); VdbeCoverage(v);
sqlite3ReleaseTempReg(pParse, regRowid);
}else{ }else{
sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regKey); Index *pPk = sqlite3PrimaryKeyIndex(pTab);
} int nPk = pPk->nKeyCol;
pE1 = sqlite3ExprAlloc(db, TK_COLUMN, 0, 0); int iPk = pParse->nMem+1;
if( pE1 ){ int i;
pE1->pTab = pTab; pParse->nMem += nPk;
pE1->iTable = pUpsert->iDataCur; for(i=0; i<nPk; i++){
pE1->iColumn = -1; int k;
} assert( pPk->aiColumn[i]>=0 );
pE2 = sqlite3ExprAlloc(db, TK_REGISTER, 0, 0); k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[i]);
if( pE2 ){ sqlite3VdbeAddOp3(v, OP_Column, iCur, k, iPk+i);
pE2->iTable = regKey;
pE2->affinity = SQLITE_AFF_INTEGER;
}
pWhere = sqlite3ExprAnd(db,pWhere,sqlite3PExpr(pParse, TK_EQ, pE1, pE2));
}else{
/* a WITHOUT ROWID table */
int i, j;
for(i=0; i<pIdx->nKeyCol; i++){
regKey = ++pParse->nMem;
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regKey);
j = pIdx->aiColumn[i];
VdbeComment((v, "%s", pTab->aCol[j].zName));
pE1 = sqlite3ExprAlloc(db, TK_COLUMN, 0, 0);
if( pE1 ){
pE1->pTab = pTab;
pE1->iTable = pUpsert->iDataCur;
pE1->iColumn = j;
} }
pE2 = sqlite3ExprAlloc(db, TK_REGISTER, 0, 0); i = sqlite3VdbeAddOp4Int(v, OP_Found, iDataCur, 0, iPk, nPk);
if( pE2 ){ VdbeCoverage(v);
pE2->iTable = regKey; sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_CORRUPT, OE_Abort);
pE2->affinity = pTab->zColAff[j]; sqlite3VdbeJumpHere(v, i);
}
pWhere = sqlite3ExprAnd(db,pWhere,sqlite3PExpr(pParse, TK_EQ, pE1, pE2));
} }
} }
/* pUpsert does not own pUpsertSrc - the outer INSERT statement does. So /* pUpsert does not own pUpsertSrc - the outer INSERT statement does. So
** we have to make a copy before passing it down into sqlite3Update() */ ** we have to make a copy before passing it down into sqlite3Update() */
pSrc = sqlite3SrcListDup(db, pUpsert->pUpsertSrc, 0); pSrc = sqlite3SrcListDup(db, pUpsert->pUpsertSrc, 0);
sqlite3Update(pParse, pSrc, pUpsert->pUpsertSet, sqlite3Update(pParse, pSrc, pUpsert->pUpsertSet,
pWhere, OE_Abort, 0, 0, pUpsert); pUpsert->pUpsertWhere, OE_Abort, 0, 0, pUpsert);
pUpsert->pUpsertSet = 0; /* Will have been deleted by sqlite3Update() */ pUpsert->pUpsertSet = 0; /* Will have been deleted by sqlite3Update() */
pUpsert->pUpsertWhere = 0; /* Will have been deleted by sqlite3Update() */
VdbeNoopComment((v, "End DO UPDATE of UPSERT")); VdbeNoopComment((v, "End DO UPDATE of UPSERT"));
} }