From: drh <> Date: Wed, 24 Aug 2022 17:55:27 +0000 (+0000) Subject: First attempt at adding a built-in prepared statement cache. This is X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4f2bf6bf8849cae1edfa73b872eeb2ebb626d188;p=thirdparty%2Fsqlite.git First attempt at adding a built-in prepared statement cache. This is mostly working, but still has a few obscure faults. FossilOrigin-Name: c217b763b148e8bece9d1f72525fd5026d3f710b337c010ffd0306fde4f9add8 --- diff --git a/ext/misc/stmt.c b/ext/misc/stmt.c index 1687f41978..a89eccf295 100644 --- a/ext/misc/stmt.c +++ b/ext/misc/stmt.c @@ -31,7 +31,7 @@ SQLITE_EXTENSION_INIT1 #ifndef SQLITE_OMIT_VIRTUALTABLE -#define STMT_NUM_INTEGER_COLUMN 10 +#define STMT_NUM_INTEGER_COLUMN 11 typedef struct StmtRow StmtRow; struct StmtRow { sqlite3_int64 iRowid; /* Rowid value */ @@ -95,11 +95,12 @@ static int stmtConnect( #define STMT_COLUMN_REPREP 8 /* SQLITE_STMTSTATUS_REPREPARE */ #define STMT_COLUMN_RUN 9 /* SQLITE_STMTSTATUS_RUN */ #define STMT_COLUMN_MEM 10 /* SQLITE_STMTSTATUS_MEMUSED */ +#define STMT_COLUMN_STATE 11 /* SQLITE_STMTSTATUS_STATE */ rc = sqlite3_declare_vtab(db, "CREATE TABLE x(sql,ncol,ro,busy,nscan,nsort,naidx,nstep," - "reprep,run,mem)"); + "reprep,run,mem,state)"); if( rc==SQLITE_OK ){ pNew = sqlite3_malloc64( sizeof(*pNew) ); *ppVtab = (sqlite3_vtab*)pNew; @@ -175,6 +176,10 @@ static int stmtColumn( StmtRow *pRow = pCur->pRow; if( i==STMT_COLUMN_SQL ){ sqlite3_result_text(ctx, pRow->zSql, -1, SQLITE_TRANSIENT); + }else if( i==STMT_COLUMN_STATE ){ + const char *azStateName[] = { "INIT", "READY", "RUN", "HALT", "CACHE" }; + int j = pRow->aCol[i]; + sqlite3_result_text(ctx, azStateName[j%5], -1, SQLITE_STATIC); }else{ sqlite3_result_int(ctx, pRow->aCol[i]); } @@ -253,6 +258,9 @@ static int stmtFilter( pNew->aCol[STMT_COLUMN_MEM] = sqlite3_stmt_status( p, SQLITE_STMTSTATUS_MEMUSED, 0 ); + pNew->aCol[STMT_COLUMN_STATE] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_STATE, 0 + ); pNew->iRowid = iRowid++; *ppRow = pNew; ppRow = &pNew->pNext; diff --git a/manifest b/manifest index 18912fab5b..9c1bc75ff2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Change\sthe\snames\sof\sthe\sVdbe.pVNext\sand\sVdbe.ppVPrev\sfields\sto\smake\sthem\nunique.\s\sChange\sto\sppVPrev\sto\ssave\sa\sfew\sbytes\sand\sa\sfew\sCPU\scycles. -D 2022-08-23T20:11:01.058 +C First\sattempt\sat\sadding\sa\sbuilt-in\sprepared\sstatement\scache.\s\sThis\sis\nmostly\sworking,\sbut\sstill\shas\sa\sfew\sobscure\sfaults. +D 2022-08-24T17:55:27.625 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -328,7 +328,7 @@ F ext/misc/shathree.c 7b17615869a495659f1569ada1d8d3d21b4a24614f2746d93cc87ef7c0 F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6 -F ext/misc/stmt.c ed05ad78013edccd43f4fc33d8244001f6f886eb2dfd2106ba33616ac514c6fb +F ext/misc/stmt.c 36ad8e9a5352e4795dd1da1d04c6ab9680c58d1dd648bdb26a53123c976a0f2b F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4 F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b @@ -552,7 +552,7 @@ F src/insert.c aea5361767817f917b0f0f647a1f0b1621bd858938ae6ae545c3b6b9814b798f F src/json.c 7749b98c62f691697c7ee536b570c744c0583cab4a89200fdd0fc2aa8cc8cbd6 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 853385cc7a604157e137585097949252d5d0c731768e16b044608e5c95c3614b -F src/main.c 211f5fd4110b0565fc2135d60ed12b4ef8a29d634414f471b5f957aa7b7f2afa +F src/main.c 5c82ba33d2032f099c90fbb2a7a05ed684164a0ddecb7aaeda4374d97f4055b5 F src/malloc.c b7a3430cbe91d3e8e04fc10c2041b3a19794e63556ad2441a13d8dadd9b2bafc F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de @@ -583,17 +583,17 @@ F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 F src/pcache1.c 0b4245cd4964e635f2630908c2533cd8e9da7af3ca592e23ae8730aa25ae5eb9 F src/pragma.c b57a859a366472131194a9ad35cd76d5920577226b04c884b1b9085605faa280 F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 -F src/prepare.c 971d5819a4bda88038c2283d71fc0a2974ffc3dd480f9bd941341017abacfd1b +F src/prepare.c 54839876dd40924279004e98ba35e38a64aed728b9be5ba8641f10a9a5203496 F src/printf.c e99ee9741e79ae3873458146f59644276657340385ade4e76a5f5d1c25793764 F src/random.c 546d6feb15ec69c1aafe9bb351a277cbb498fd5410e646add673acb805714960 F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c ccce37e7fbe71089cf6aec91e7134c9c0c1d4840cff9f02587bbc71240d914a5 F src/shell.c.in 269f682249c1bce2962883e5b99c8702b16a488a43b9ae186daa178713a93c5d -F src/sqlite.h.in b9b7fd73239d94db20332bb6e504688001e5564b655e1318a4427a1caef4b99e +F src/sqlite.h.in f34d1ca4091c969e1b4b4744839f415c7963618bb10b34bbe1ef6a356ace0840 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h a988810c9b21c0dc36dc7a62735012339dc76fc7ab448fb0792721d30eacb69d -F src/sqliteInt.h ebf18764e404a2cef39ae5bfc8dd4a83bf0d70be1c444a4fbd8539eb35ef6ffd +F src/sqliteInt.h 71316870f7ef94fd8326d435c2b2fb6a5e374133f128fd8c0132502ad6d18b6f F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -661,10 +661,10 @@ F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/util.c 602fe229f32a96ceccae4f40824129669582096f7c355f53dbac156c9fecef23 F src/vacuum.c bb346170b0b54c6683bba4a5983aea40485597fdf605c87ec8bc2e199fe88cd8 F src/vdbe.c 0150d16da21cb96b2b0f2880aad6acd80ddedf93a9f3eb2c5200aef864854fe6 -F src/vdbe.h 64619af62603dc3c4f5ff6ff6d2c8f389abd667a29ce6007ed44bd22b3211cd0 -F src/vdbeInt.h 17b7461ffcf9ee760d1341731715a419f6b8c763089a7ece25c2e8098d702b3f -F src/vdbeapi.c fc3183daf72808b4311b228989120fdbc2dc44972fb0d77d5c453460cc0e5b2c -F src/vdbeaux.c 1f33d20fc19f13ad781ee083276ffad2204a182617859895db9daa3412456457 +F src/vdbe.h c4c9defdf2ad9465f9c9c7f79c3b03555f47aa930ea2744f358d9b27269763c7 +F src/vdbeInt.h e332f7d165b2cb984772c425c45f67f1d57e3c032d8dbf74a9ef8f1cebfa4bb2 +F src/vdbeapi.c 8087dba84836f59d0d5340a6c554948cf4fef03ace62a8d7dd3c8f310107e3f4 +F src/vdbeaux.c fdbf1df03cfcf4d11c49178be30b23d2b750082edd4d7b6aa663479e7dc65f7c F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd F src/vdbemem.c c3ce80af15e2ff5c2824a8db881681cbf511376f13613da020bac6d320c535b1 F src/vdbesort.c 43756031ca7430f7aec3ef904824a7883c4ede783e51f280d99b9b65c0796e35 @@ -1480,7 +1480,7 @@ F test/speed3.test 694affeb9100526007436334cf7d08f3d74b85ef F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715 F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa F test/speed4p.test 377a0c48e5a92e0b11c1c5ebb1bc9d83a7312c922bc0cb05970ef5d6a96d1f0c -F test/speedtest1.c 61f8a72bbcc80edb0b95e957f108feb4013ff3d08721cc87ae1865fd4d20652d +F test/speedtest1.c d37467faf7a43711b134f05e9cde7abfd1504bf8f16f6220f67d8db1bc0fc51f F test/spellfix.test 951a6405d49d1a23d6b78027d3877b4a33eeb8221dcab5704b499755bb4f552e F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3 F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33 @@ -1957,7 +1957,7 @@ F tool/showstat4.c 0682ebea7abf4d3657f53c4a243f2e7eab48eab344ed36a94bb75dcd19a5c F tool/showwal.c 0253c187ae16fdae9cde89e63e1dfcd3bb35e5416d066415f99e2f8cac6ab03d F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe F tool/spaceanal.tcl 1b5be34c6223cb1af06da2a10fb77863eb869b1962d055820b0a11cf2336ab45 -F tool/speed-check.sh ff74a68bb95a0341275f4d3c9a7d8a3800bd278aceecf1913295a1f0175bc3e6 +F tool/speed-check.sh fc224ee49062fedf3bbef87779197bb5103f6f796603e4255ea558633360e8ba F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355 F tool/speedtest16.c ecb6542862151c3e6509bbc00509b234562ae81e F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff @@ -1999,8 +1999,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4e0a07fc6f96e6e7726506b7a5ee942461c1381501f253893fa94d76023b634f -R 72b6d89c208e8d4296a211e6207c4905 +P 34b8ea31877ae8b40729d37b3f51ae7e15f38be841881ea4a37c9c8f0a52896d +R 08d11f60d527a9ebc32a98574ca5f0f9 +T *branch * stmt-cache +T *sym-stmt-cache * +T -sym-trunk * U drh -Z 0897b771860025dba1bfebc2fa438b5b +Z c8d2ef694a3736c7eee4ba211f6abfcb # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index d5837ab0a9..9e6622d10d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -34b8ea31877ae8b40729d37b3f51ae7e15f38be841881ea4a37c9c8f0a52896d \ No newline at end of file +c217b763b148e8bece9d1f72525fd5026d3f710b337c010ffd0306fde4f9add8 \ No newline at end of file diff --git a/src/main.c b/src/main.c index d982c3dfb7..ac09341eb8 100644 --- a/src/main.c +++ b/src/main.c @@ -917,6 +917,16 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){ int rc; va_start(ap, op); switch( op ){ + case SQLITE_DBCONFIG_STMTCACHE_SIZE: { + int szDesired = va_arg(ap,int); + int *pszNew = va_arg(ap,int*); + if( szDesired>=0 ){ + sqlite3VdbeChangeStmtCacheSize(db, szDesired); + } + *pszNew = (int)db->mxCache; + rc = SQLITE_OK; + break; + } case SQLITE_DBCONFIG_MAINDBNAME: { /* IMP: R-06824-28531 */ /* IMP: R-36257-52125 */ @@ -1212,6 +1222,7 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){ } /* Force xDisconnect calls on all virtual tables */ + sqlite3VdbeChangeStmtCacheSize(db, 0); disconnectAllVtab(db); /* If a transaction is open, the disconnectAllVtab() call above @@ -3250,6 +3261,7 @@ static int openDatabase( db->autoCommit = 1; db->nextAutovac = -1; db->szMmap = sqlite3GlobalConfig.szMmap; + db->mxCache = SQLITE_DEFAULT_STMTCACHE_SIZE; db->nextPagesize = 0; db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */ #ifdef SQLITE_ENABLE_SORTER_MMAP diff --git a/src/prepare.c b/src/prepare.c index f66c366b99..0ac4c8d97b 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -686,6 +686,7 @@ static int sqlite3Prepare( int rc = SQLITE_OK; /* Result code */ int i; /* Loop counter */ Parse sParse; /* Parsing context */ + u32 hSql = 0; /* Hash of the input SQL */ /* sqlite3ParseObjectInit(&sParse, db); // inlined for performance */ memset(PARSE_HDR(&sParse), 0, PARSE_HDR_SZ); @@ -698,10 +699,24 @@ static int sqlite3Prepare( if( db->mallocFailed ) sqlite3ErrorMsg(&sParse, "out of memory"); assert( sqlite3_mutex_held(db->mutex) ); - /* For a long-term use prepared statement avoid the use of - ** lookaside memory. - */ if( prepFlags & SQLITE_PREPARE_PERSISTENT ){ + /* Check to see if the prepared statement can be resolved from cache. */ + if( db->mxCache>0 ){ + Vdbe *pCache; + if( nBytes<0 ) nBytes = sqlite3Strlen30(zSql); + pCache = sqlite3VdbeFindInStmtCache(db, zSql, nBytes, &hSql); + if( pCache ){ + *ppStmt = (sqlite3_stmt*)pCache; + if( pzTail ){ + *pzTail = &zSql[nBytes]; + } + goto end_prepare; + } + } + + /* For a long-term use prepared statement should avoid the use of + ** lookaside memory. + */ sParse.disableLookaside++; DisableLookaside; } @@ -776,7 +791,8 @@ static int sqlite3Prepare( } if( db->init.busy==0 ){ - sqlite3VdbeSetSql(sParse.pVdbe, zSql, (int)(sParse.zTail-zSql), prepFlags); + sqlite3VdbeSetSql(sParse.pVdbe, zSql, (int)(sParse.zTail-zSql), + prepFlags, hSql); } if( db->mallocFailed ){ sParse.rc = SQLITE_NOMEM_BKPT; diff --git a/src/sqlite.h.in b/src/sqlite.h.in index d862c4fc00..8ebf904cb9 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -2128,7 +2128,61 @@ struct sqlite3_mem_methods { ** non-zero [error code] if a discontinued or unsupported configuration option ** is invoked. ** +** Most of these configuration options are used to set, clear, or query flags. +** All such options take two arguments: And integer (X) and a pointer to an +** integer (P). If the X argument is greater than zero, the flag is set. If +** X is equal to zero, the flag is cleared. If X is less than zero, the flag +** remains unchanged. In all cases, the value of the flag after any changes +** (either 0 or 1) is written into the integer pointed to by P. All of the +** configuration options listed below operate this way, with the following +** exceptions: +** +** +** **
+** [[SQLITE_DBCONFIG_STMTCACHE_SIZE]]
SQLITE_DBCONFIG_STMTCACHE_SIZE
+**
The SQLITE_DBCONFIG_STMTCACHE_SIZE option is used to query or change +** the maximum number of [prepared statements] that SQLite will cache. Prepared +** statements are only candidates for caching if they are prepared using +** [sqlite3_prepare_v3()] with the [SQLITE_PREPARE_PERSISTENT] option. If +** SQLite chooses to cache a perpared statement, that means that when +** [sqlite3_finalize()] is called on the prepared statement, the statement is +** not immediately deleted, but instead is put into the cache. Subsequent +** calls to any of the [sqlite3_prepare()] ineterfaces with the exact same +** SQL text may reuse the cached prepared statement rather than creating a new +** prepared statement. This setting adjusts the maximum number of prepared +** statements that SQLite will keep in cache. The default value is +** SQLITE_DEFAULT_STMTCACHE_SIZE which is 10 unless overridden at compile-time. +** Setting the maximum cache size to zero disables the cache. The cache is +** automatically trimmed back to its maximum size when the cache size is +** reduced. Hence the cache can be flushed, by set the cache size to zero +** then set it back to its original value. +**

This configuration option takes two parameters. The first parameter (N) is +** an integer which is the desired new size of the statement cache. A negative +** value for N leaves the cache size unchanged. The second parameter (P) +** is a pointer to an integer into which the new cache size is written. +** The database connection is free to limit or truncate the N value that is +** passed in, so the new cache size returned in P is not necessarily the same +** as the N argument. +**

+** +** [[SQLITE_DBCONFIG_MAINDBNAME]]
SQLITE_DBCONFIG_MAINDBNAME
+**
^This option is used to change the name of the "main" database +** schema. ^The sole argument is a pointer to a constant UTF8 string +** which will become the new schema name in place of "main". ^SQLite +** does not make a copy of the new main schema name string, so the application +** must ensure that the argument passed into this DBCONFIG option is unchanged +** until after the database connection closes. The application is responsible +** for managing the storage associated with the string passed in as the +** argument. SQLite does not attempt to free the string, or do anything else +** to reclaim storage space associated with the string, when the database +** connection closes. +**
+** ** [[SQLITE_DBCONFIG_LOOKASIDE]] **
SQLITE_DBCONFIG_LOOKASIDE
**
^This option takes three additional arguments that determine the @@ -2229,15 +2283,6 @@ struct sqlite3_mem_methods { ** be a NULL pointer, in which case the new setting is not reported back. **
** -** [[SQLITE_DBCONFIG_MAINDBNAME]]
SQLITE_DBCONFIG_MAINDBNAME
-**
^This option is used to change the name of the "main" database -** schema. ^The sole argument is a pointer to a constant UTF8 string -** which will become the new schema name in place of "main". ^SQLite -** does not make a copy of the new main schema name string, so the application -** must ensure that the argument passed into this DBCONFIG option is unchanged -** until after the database connection closes. -**
-** ** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]] **
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
**
Usually, when a database in wal mode is closed or detached from a @@ -2396,6 +2441,7 @@ struct sqlite3_mem_methods { **
**
*/ +#define SQLITE_DBCONFIG_STMTCACHE_SIZE 999 /* int int* */ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ #define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ @@ -4047,11 +4093,19 @@ int sqlite3_limit(sqlite3*, int id, int newVal); **
The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler ** to return an error (error code SQLITE_ERROR) if the statement uses ** any virtual tables. +** +** [[SQLITE_PREPARE_CACHE]] SQLITE_PREPARE_CACHE +**
The SQLITE_PREPARE_CACHE flag gives SQLite permission to attempt +** to cache the prepared statement after it is +** [sqlite3_finalized|finalized] and attempt to reuse it as the +** return value for the next call to a [sqlite3_prepare()] that has +** the same SQL. This flag also implies [SQLITE_PREPARE_PERSISTENT]. ** */ #define SQLITE_PREPARE_PERSISTENT 0x01 #define SQLITE_PREPARE_NORMALIZE 0x02 #define SQLITE_PREPARE_NO_VTAB 0x04 +#define SQLITE_PREPARE_CACHE 0x08 /* ** CAPI3REF: Compiling An SQL Statement @@ -8576,6 +8630,7 @@ int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); #define SQLITE_STMTSTATUS_RUN 6 #define SQLITE_STMTSTATUS_FILTER_MISS 7 #define SQLITE_STMTSTATUS_FILTER_HIT 8 +#define SQLITE_STMTSTATUS_STATE 98 #define SQLITE_STMTSTATUS_MEMUSED 99 /* diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 21d15378f1..b7fe75855f 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -347,6 +347,16 @@ # define SQLITE_DEFAULT_MEMSTATUS 1 #endif +/* +** Set the default maximum statement cache size +*/ +#if !defined(SQLITE_DEFAULT_STMTCACHE_SIZE) +# define SQLITE_DEFAULT_STMTCACHE_SIZE 10 +#endif +#if SQLITE_DEFAULT_STMTCACHE_SIZE<0 || SQLITE_DEFAULT_STMTCACHE_SIZE>254 +# error SQLITE_DEFAULT_STMTCACHE_SIZE must be between 0 and 254 +#endif + /* ** Exactly one of the following macros must be defined in order to ** specify which memory allocation subsystem to use. @@ -1560,6 +1570,7 @@ struct sqlite3 { u8 noSharedCache; /* True if no shared-cache backends */ u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */ u8 eOpenState; /* Current condition of the connection */ + u8 mxCache, nCache; /* Max and current size of statement cache */ int nextPagesize; /* Pagesize after VACUUM if >0 */ i64 nChange; /* Value returned by sqlite3_changes() */ i64 nTotalChange; /* Value returned by sqlite3_total_changes() */ diff --git a/src/vdbe.h b/src/vdbe.h index eb1445f1db..324e811e08 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -262,7 +262,9 @@ int sqlite3VdbeSetColName(Vdbe*, int, int, const char *, void(*)(void*)); void sqlite3VdbeCountChanges(Vdbe*); sqlite3 *sqlite3VdbeDb(Vdbe*); u8 sqlite3VdbePrepareFlags(Vdbe*); -void sqlite3VdbeSetSql(Vdbe*, const char *z, int n, u8); +void sqlite3VdbeSetSql(Vdbe*, const char *z, int n, u8, u32); +Vdbe *sqlite3VdbeFindInStmtCache(sqlite3*,const char*,int,u32*); +void sqlite3VdbeChangeStmtCacheSize(sqlite3*,int); #ifdef SQLITE_ENABLE_NORMALIZE void sqlite3VdbeAddDblquoteStr(sqlite3*,Vdbe*,const char*); int sqlite3VdbeUsesDoubleQuotedString(Vdbe*,const char*); diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 0c6301c8f1..a17e1e3ba8 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -470,6 +470,8 @@ struct Vdbe { yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ u32 aCounter[9]; /* Counters used by sqlite3_stmt_status() */ + u32 hSql; /* Hash for zSql */ + int nSql; /* Number of bytes in zSql[] */ char *zSql; /* Text of the SQL statement that generated this */ #ifdef SQLITE_ENABLE_NORMALIZE char *zNormSql; /* Normalization of the associated SQL statement */ @@ -496,6 +498,7 @@ struct Vdbe { #define VDBE_READY_STATE 1 /* Ready to run but not yet started */ #define VDBE_RUN_STATE 2 /* Run in progress */ #define VDBE_HALT_STATE 3 /* Finished. Need reset() or finalize() */ +#define VDBE_CACHE_STATE 4 /* In statement cache */ /* ** Structure used to store the context required by the @@ -653,9 +656,11 @@ int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int, int *); #ifdef SQLITE_DEBUG void sqlite3VdbeIncrWriteCounter(Vdbe*, VdbeCursor*); void sqlite3VdbeAssertAbortable(Vdbe*); + void sqlite3VdbeCheckActiveVdbeCount(sqlite3*); #else # define sqlite3VdbeIncrWriteCounter(V,C) # define sqlite3VdbeAssertAbortable(V) +# define sqlite3VdbeCheckActiveVdbeCount(D) #endif #if !defined(SQLITE_OMIT_SHARED_CACHE) diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 6c5f7562b3..f42d8ca6f3 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -110,7 +110,20 @@ int sqlite3_finalize(sqlite3_stmt *pStmt){ checkProfileCallback(db, v); assert( v->eVdbeState>=VDBE_READY_STATE ); rc = sqlite3VdbeReset(v); - sqlite3VdbeDelete(v); + if( rc==SQLITE_OK + && v->hSql>0 + && db->mxCache>0 + && v->expired==0 + ){ + v->eVdbeState = VDBE_CACHE_STATE; + db->nCache++; + if( db->nCache>db->mxCache){ + sqlite3VdbeChangeStmtCacheSize(db, db->mxCache); + } + }else{ + sqlite3VdbeDelete(v); + } + sqlite3VdbeCheckActiveVdbeCount(db); rc = sqlite3ApiExit(db, rc); sqlite3LeaveMutexAndCloseZombie(db); } @@ -689,6 +702,7 @@ static int sqlite3Step(Vdbe *p){ if( p->bIsReader ) db->nVdbeRead++; p->pc = 0; p->eVdbeState = VDBE_RUN_STATE; + sqlite3VdbeCheckActiveVdbeCount(db); }else if( ALWAYS(p->eVdbeState==VDBE_HALT_STATE) ){ @@ -1842,6 +1856,8 @@ int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ db->pnBytesFreed = 0; db->lookaside.pEnd = db->lookaside.pTrueEnd; sqlite3_mutex_leave(db->mutex); + }else if( op==SQLITE_STMTSTATUS_STATE ){ + v = pVdbe->eVdbeState; }else{ v = pVdbe->aCounter[op]; if( resetFlag ) pVdbe->aCounter[op] = 0; diff --git a/src/vdbeaux.c b/src/vdbeaux.c index a4b4516c04..916b2277e3 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -67,7 +67,13 @@ void sqlite3VdbeError(Vdbe *p, const char *zFormat, ...){ /* ** Remember the SQL string for a prepared statement. */ -void sqlite3VdbeSetSql(Vdbe *p, const char *z, int n, u8 prepFlags){ +void sqlite3VdbeSetSql( + Vdbe *p, /* The VDBE whose SQL text is to be set */ + const char *z, /* the SQL text */ + int n, /* Number of bytes in z[] */ + u8 prepFlags, /* prepFlags */ + u32 hSql /* Hash of z[] used for fast cache lookups */ +){ if( p==0 ) return; p->prepFlags = prepFlags; if( (prepFlags & SQLITE_PREPARE_SAVESQL)==0 ){ @@ -75,8 +81,61 @@ void sqlite3VdbeSetSql(Vdbe *p, const char *z, int n, u8 prepFlags){ } assert( p->zSql==0 ); p->zSql = sqlite3DbStrNDup(p->db, z, n); + p->nSql = n; + p->hSql = hSql; +} + +/* +** Search for a cached VDBE that that implements the given SQL. +*/ +Vdbe *sqlite3VdbeFindInStmtCache( + sqlite3 *db, /* The database connection holding the cache */ + const char *zSql, /* Input SQL text */ + int nSql, /* Size of zSql[] in bytes */ + u32 *phSql /* Write the hash value here if not found */ +){ + int i; /* Loop counter */ + Vdbe *pCache; /* Candidate statement */ + u32 c; /* One character of SQL input */ + u32 hSql; /* A hash of the SQL input */ + int n; /* Number of SQL input characters to be hashed */ + + hSql = 0; + n = nSql; + if( n>100 ) n = 100; + for(i=0; inCache ){ + for(pCache = db->pVdbe; pCache; pCache=pCache->pVNext){ + if( pCache->hSql==hSql + && pCache->eVdbeState==VDBE_CACHE_STATE + && pCache->nSql==nSql + && strncmp(zSql, pCache->zSql, nSql)==0 + ){ + pCache->eVdbeState = VDBE_READY_STATE; + db->nCache--; + if( pCache!=db->pVdbe ){ + if( pCache->pVNext ){ + pCache->pVNext->ppVPrev = pCache->ppVPrev; + } + *pCache->ppVPrev = pCache->pVNext; + pCache->pVNext = db->pVdbe; + pCache->ppVPrev = &db->pVdbe; + db->pVdbe->ppVPrev = &pCache->pVNext; + db->pVdbe = pCache; + } + sqlite3VdbeCheckActiveVdbeCount(db); + return pCache; + } + } + } + *phSql = hSql; + return 0; } + #ifdef SQLITE_ENABLE_NORMALIZE /* ** Add a new element to the Vdbe->pDblStr list. @@ -3008,7 +3067,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ ** This is a no-op if NDEBUG is defined. */ #ifndef NDEBUG -static void checkActiveVdbeCnt(sqlite3 *db){ +void sqlite3VdbeCheckActiveVdbeCount(sqlite3 *db){ Vdbe *p; int cnt = 0; int nWrite = 0; @@ -3026,10 +3085,36 @@ static void checkActiveVdbeCnt(sqlite3 *db){ assert( nWrite==db->nVdbeWrite ); assert( nRead==db->nVdbeRead ); } -#else -#define checkActiveVdbeCnt(x) #endif +/* +** Attempt to change the statement cache size. If the statement cache +** size is reduced remove excess elements from the cache. +** +** The argument is the *desired* new statement cache size. The new +** statement cache size will not necessarily be the same size. +*/ +void sqlite3VdbeChangeStmtCacheSize(sqlite3 *db, int szDesired){ + assert( szDesired>=0 ); + if( szDesired>100 ) szDesired = 100; + if( db->nCache>szDesired ){ + Vdbe *p, *pNext; + int n = szDesired; + for(p=db->pVdbe; p; p=pNext){ + pNext = p->pVNext; + if( p->hSql==0 ) continue; + if( p->eVdbeState!=VDBE_CACHE_STATE ) continue; + if( n>0 ){ n--; continue; } + sqlite3VdbeDelete(p); + assert( db->nCache>szDesired ); + db->nCache--; + } + assert( db->nCache==szDesired ); + } + db->mxCache = szDesired; + sqlite3VdbeCheckActiveVdbeCount(db); +} + /* ** If the Vdbe passed as the first argument opened a statement-transaction, ** close it now. Argument eOp must be either SAVEPOINT_ROLLBACK or @@ -3158,7 +3243,7 @@ int sqlite3VdbeHalt(Vdbe *p){ p->rc = SQLITE_NOMEM_BKPT; } closeAllCursors(p); - checkActiveVdbeCnt(db); + sqlite3VdbeCheckActiveVdbeCount(db); /* No commit or rollback needed if the program never started or if the ** SQL statement does not read or write a database file. */ @@ -3317,7 +3402,7 @@ int sqlite3VdbeHalt(Vdbe *p){ assert( db->nVdbeRead>=db->nVdbeWrite ); assert( db->nVdbeWrite>=0 ); p->eVdbeState = VDBE_HALT_STATE; - checkActiveVdbeCnt(db); + sqlite3VdbeCheckActiveVdbeCount(db); if( db->mallocFailed ){ p->rc = SQLITE_NOMEM_BKPT; } @@ -5104,6 +5189,12 @@ void sqlite3ExpirePreparedStatements(sqlite3 *db, int iCode){ for(p = db->pVdbe; p; p=p->pVNext){ p->expired = iCode+1; } + if( db->nCache>0 ){ + u8 mxCache = db->mxCache; + sqlite3VdbeChangeStmtCacheSize(db, 0); + assert( db->nCache==0 ); + db->mxCache = mxCache; + } } /* diff --git a/test/speedtest1.c b/test/speedtest1.c index 83c503c8c7..e782e7313c 100644 --- a/test/speedtest1.c +++ b/test/speedtest1.c @@ -25,6 +25,7 @@ static const char zHelp[] = " --output FILE Store SQL output in FILE\n" " --pagesize N Set the page size to N\n" " --pcache N SZ Configure N pages of pagecache each of size SZ bytes\n" + " --persist Use the SQLITE_PREPARE_PERSISTENT flag\n" " --primarykey Use PRIMARY KEY instead of UNIQUE where appropriate\n" " --repeat N Repeat each SELECT N times (default: 1)\n" " --reprepare Reprepare each statement upon every invocation\n" @@ -87,6 +88,7 @@ static struct Global { sqlite3_int64 iTotal; /* Total time */ int bWithoutRowid; /* True for --without-rowid */ int bReprepare; /* True to reprepare the SQL on each rerun */ + unsigned int mPrepFlags; /* Flags passed into sqlite3_prepare_v3() */ int bSqlOnly; /* True to print the SQL once only */ int bExplain; /* Print SQL with EXPLAIN prefix */ int bVerify; /* Try to verify that results are correct */ @@ -496,7 +498,7 @@ char *speedtest1_once(const char *zFormat, ...){ if( g.bSqlOnly ){ printSql(zSql); }else{ - int rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, 0); + int rc = sqlite3_prepare_v3(g.db, zSql, -1, 0, &pStmt, 0); if( rc ){ fatal_error("SQL error: %s\n", sqlite3_errmsg(g.db)); } @@ -523,7 +525,7 @@ void speedtest1_prepare(const char *zFormat, ...){ }else{ int rc; if( g.pStmt ) sqlite3_finalize(g.pStmt); - rc = sqlite3_prepare_v2(g.db, zSql, -1, &g.pStmt, 0); + rc = sqlite3_prepare_v3(g.db, zSql, -1, g.mPrepFlags, &g.pStmt, 0); if( rc ){ fatal_error("SQL error: %s\n", sqlite3_errmsg(g.db)); } @@ -586,8 +588,16 @@ void speedtest1_run(void){ #if SQLITE_VERSION_NUMBER>=3006001 if( g.bReprepare ){ sqlite3_stmt *pNew; - sqlite3_prepare_v2(g.db, sqlite3_sql(g.pStmt), -1, &pNew, 0); - sqlite3_finalize(g.pStmt); + if( g.mPrepFlags & SQLITE_PREPARE_PERSISTENT ){ + char zBuf[1000]; + strncpy(zBuf, sqlite3_sql(g.pStmt), sizeof(zBuf)); + zBuf[sizeof(zBuf)-1] = 0; + sqlite3_finalize(g.pStmt); + sqlite3_prepare_v3(g.db, zBuf, -1, g.mPrepFlags, &pNew, 0); + }else{ + sqlite3_prepare_v2(g.db, sqlite3_sql(g.pStmt), -1, &pNew, 0); + sqlite3_finalize(g.pStmt); + } g.pStmt = pNew; }else #endif @@ -2273,6 +2283,8 @@ int main(int argc, char **argv){ if( i>=argc-1 ) fatal_error("missing arguments on %s\n", argv[i]); g.nRepeat = integerValue(argv[i+1]); i += 1; + }else if( strcmp(z,"persist")==0 ){ + g.mPrepFlags |= SQLITE_PREPARE_PERSISTENT; }else if( strcmp(z,"reprepare")==0 ){ g.bReprepare = 1; #if SQLITE_VERSION_NUMBER>=3006000 diff --git a/tool/speed-check.sh b/tool/speed-check.sh index 6b0fbeb43a..6d69ea0320 100644 --- a/tool/speed-check.sh +++ b/tool/speed-check.sh @@ -143,6 +143,9 @@ while test "$1" != ""; do SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset rtree" CC_OPTS="$CC_OPTS -DSQLITE_ENABLE_RTREE" ;; + --persist) + SPEEDTEST_OPTS="$SPEEDTEST_OPTS --persist" + ;; --orm) SPEEDTEST_OPTS="$SPEEDTEST_OPTS --testset orm" ;;