Mercurial > hg > orthanc
changeset 7150:d0627441796a default tip
SQLite: run ANALYZE or OPTIMIZE to improve query planner
| author | Alain Mazy <am@orthanc.team> |
|---|---|
| date | Wed, 16 Sep 2026 14:55:01 +0200 |
| parents | 5c5392a01134 |
| children | |
| files | NEWS OrthancFramework/Sources/SQLite/Connection.cpp OrthancFramework/Sources/SQLite/Connection.h OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp |
| diffstat | 4 files changed, 52 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Wed Sep 16 14:54:38 2026 +0200 +++ b/NEWS Wed Sep 16 14:55:01 2026 +0200 @@ -1,6 +1,14 @@ Pending changes in the mainline =============================== +General +------- + +* With the default SQLite database: + - Optimized some queries to access single resources. + - Optimized many queries thanks to improved query planning. An Optimization step can + sometimes run at startup which slows down the Orthanc startup. + Plugin SDK ----------
--- a/OrthancFramework/Sources/SQLite/Connection.cpp Wed Sep 16 14:54:38 2026 +0200 +++ b/OrthancFramework/Sources/SQLite/Connection.cpp Wed Sep 16 14:55:01 2026 +0200 @@ -232,6 +232,31 @@ return false; } + void Connection::Optimize(bool onlyIfStatsDontExists) + { + if (onlyIfStatsDontExists) + { + if (!DoesTableExist("sqlite_stat1")) // create the table if it does not exist yet and if there are enough data in the Resources table + { + Statement countResources(*this, "SELECT COUNT(*) FROM Resources"); + countResources.Step(); + if (countResources.ColumnInt64(0) > 1000) // no need to ANALYZE if there are not enough data in the table + { + CLOG(WARNING, SQLITE) << "SQLite: Performing first ANALYZE to improve the query planner"; + Statement analyze(*this, "ANALYZE"); + analyze.Run(); + } + } + } + else + { + CLOG(WARNING, SQLITE) << "SQLite: Performing OPTIMIZE to improve the query planner"; + Statement analyze(*this, "PRAGMA OPTIMIZE"); // OPTIMIZE runs ANALYZE only if it makes sense + analyze.Run(); + CLOG(WARNING, SQLITE) << "SQLite: OPTIMIZE completed"; + } + } + int64_t Connection::GetLastInsertRowId() const { return sqlite3_last_insert_rowid(db_);
--- a/OrthancFramework/Sources/SQLite/Connection.h Wed Sep 16 14:54:38 2026 +0200 +++ b/OrthancFramework/Sources/SQLite/Connection.h Wed Sep 16 14:55:01 2026 +0200 @@ -121,6 +121,8 @@ void FlushToDisk(); + void Optimize(bool onlyIfStatsDontExists); + IScalarFunction* Register(IScalarFunction* func); // Takes the ownership of the function // Info querying -------------------------------------------------------------
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Wed Sep 16 14:54:38 2026 +0200 +++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Wed Sep 16 14:55:01 2026 +0200 @@ -1094,6 +1094,16 @@ sql += " ORDER BY c0_queryId, c2_rowNumber"; // this is really important to make sure that the Lookup query is the first one to provide results since we use it to create the responses element ! + // uncomment to show the execution plan (in dev mode only of course !) + // { + // SQLite::Statement analyze(db_, SQLITE_FROM_HERE_DYNAMIC(std::string("EXPLAIN QUERY PLAN ") + sql), std::string("EXPLAIN QUERY PLAN ") + sql); + // CLOG(TRACE, SQLITE) << "EXPLAIN QUERY PLAN for " << sql; + // while (analyze.Step()) + // { + // CLOG(TRACE, SQLITE) << "id: " << analyze.ColumnString(0) << " parent: " << analyze.ColumnString(1) << " op: " << analyze.ColumnString(3); + // } + // } + SQLite::Statement s(db_, SQLITE_FROM_HERE_DYNAMIC(sql), sql); formatter.Bind(s); @@ -2873,6 +2883,9 @@ LOG(INFO) << "Adding timeout column to the \"Queues\" table"; ExecuteEmbeddedScript(db_, ServerResources::ADD_TIMEOUT_TO_QUEUES); } + + // New in Orthanc 1.13.1 run optimize at each startup + db_.Optimize(false); } transaction->Commit(0); @@ -3010,6 +3023,10 @@ void SQLiteDatabaseWrapper::FlushToDisk() { boost::recursive_mutex::scoped_lock lock(mutex_); + + // this will run ANALYZE only once whe the Resources table contains enough rows + db_.Optimize(true); + db_.FlushToDisk(); }
