diff --git a/common/database/database_connection.cpp b/common/database/database_connection.cpp
index 8004984d6b..50844c22f6 100644
--- a/common/database/database_connection.cpp
+++ b/common/database/database_connection.cpp
@@ -43,6 +43,7 @@
 #include <wx/log.h>
 
 #include <database/database_connection.h>
+#include <database/database_cache.h>
 
 
 const char* const traceDatabase = "KICAD_DATABASE";
@@ -88,6 +89,8 @@ DATABASE_CONNECTION::DATABASE_CONNECTION( const std::string& aDataSourceName,
     m_pass    = aPassword;
     m_timeout = aTimeoutSeconds;
 
+    m_cache = std::make_unique<DATABASE_CACHE>( 10, 1 );
+
     if( aConnectNow )
         Connect();
 }
@@ -100,6 +103,8 @@ DATABASE_CONNECTION::DATABASE_CONNECTION( const std::string& aConnectionString,
     m_connectionString = aConnectionString;
     m_timeout          = aTimeoutSeconds;
 
+    m_cache = std::make_unique<DATABASE_CACHE>( 10, 1 );
+
     if( aConnectNow )
         Connect();
 }
@@ -112,6 +117,22 @@ DATABASE_CONNECTION::~DATABASE_CONNECTION()
 }
 
 
+void DATABASE_CONNECTION::SetCacheParams( int aMaxSize, int aMaxAge )
+{
+    if( !m_cache )
+        return;
+
+    if( aMaxSize < 0 )
+        aMaxSize = 0;
+
+    if( aMaxAge < 0 )
+        aMaxAge = 0;
+
+    m_cache->SetMaxSize( static_cast<size_t>( aMaxSize ) );
+    m_cache->SetMaxAge( static_cast<time_t>( aMaxAge ) );
+}
+
+
 bool DATABASE_CONNECTION::Connect()
 {
     nanodbc::string dsn  = fromUTF8( m_dsn );
@@ -301,11 +322,19 @@ bool DATABASE_CONNECTION::SelectOne( const std::string& aTable,
 
     const std::string& columnName = columnCacheIter->first;
 
-    nanodbc::statement statement( *m_conn );
+    std::string queryStr = fmt::format( "SELECT * FROM {}{}{} WHERE {}{}{} = ?",
+                                        m_quoteChar, tableName, m_quoteChar,
+                                        m_quoteChar, columnName, m_quoteChar );
 
-    nanodbc::string query = fromUTF8( fmt::format( "SELECT * FROM {}{}{} WHERE {}{}{} = ?",
-                                                   m_quoteChar, tableName, m_quoteChar,
-                                                   m_quoteChar, columnName, m_quoteChar ) );
+    nanodbc::statement statement( *m_conn );
+    nanodbc::string query = fromUTF8( queryStr );
+
+    if( m_cache->Get( queryStr, aResult ) )
+    {
+        wxLogTrace( traceDatabase, wxT( "SelectOne: `%s` with parameter `%s` - cache hit" ),
+                    toUTF8( query ), aWhere.second );
+        return true;
+    }
 
     try
     {
@@ -364,6 +393,8 @@ bool DATABASE_CONNECTION::SelectOne( const std::string& aTable,
         return false;
     }
 
+    m_cache->Put( queryStr, aResult );
+
     return true;
 }
 
diff --git a/common/database/database_lib_settings.cpp b/common/database/database_lib_settings.cpp
index 5cc9777125..c62ce31008 100644
--- a/common/database/database_lib_settings.cpp
+++ b/common/database/database_lib_settings.cpp
@@ -96,6 +96,10 @@ DATABASE_LIB_SETTINGS::DATABASE_LIB_SETTINGS( const std::string& aFilename ) :
                 }
             },
             {} ) );
+
+    m_params.emplace_back( new PARAM<int>( "cache.max_size", &m_Cache.max_size, 256 ) );
+
+    m_params.emplace_back( new PARAM<int>( "cache.max_age", &m_Cache.max_age, 10 ) );
 }
 
 
diff --git a/eeschema/sch_plugins/database/sch_database_plugin.cpp b/eeschema/sch_plugins/database/sch_database_plugin.cpp
index 6eac594fef..fdede46178 100644
--- a/eeschema/sch_plugins/database/sch_database_plugin.cpp
+++ b/eeschema/sch_plugins/database/sch_database_plugin.cpp
@@ -236,6 +236,8 @@ void SCH_DATABASE_PLUGIN::ensureConnection()
 
             THROW_IO_ERROR( msg );
         }
+
+        m_conn->SetCacheParams( m_settings->m_Cache.max_size, m_settings->m_Cache.max_age );
     }
 }
 
diff --git a/include/database/database_cache.h b/include/database/database_cache.h
new file mode 100644
index 0000000000..b8f09be086
--- /dev/null
+++ b/include/database/database_cache.h
@@ -0,0 +1,100 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Jon Evans <jon@craftyjon.com>
+ * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KICAD_DATABASE_CACHE_H
+#define KICAD_DATABASE_CACHE_H
+
+#include <chrono>
+#include <list>
+#include <string>
+#include <unordered_map>
+
+#include <database/database_connection.h>
+
+
+class DATABASE_CACHE
+{
+public:
+    typedef std::pair<std::string, std::pair<time_t, DATABASE_CONNECTION::ROW>> CACHE_ENTRY;
+
+    DATABASE_CACHE( size_t aMaxSize, time_t aMaxAge ) :
+            m_maxSize( aMaxSize ),
+            m_maxAge( aMaxAge )
+    {}
+
+    void Put( const std::string& aQuery, const DATABASE_CONNECTION::ROW& aResult )
+    {
+        auto it = m_cache.find( aQuery );
+
+        time_t time = std::chrono::system_clock::to_time_t( std::chrono::system_clock::now() );
+
+        m_cacheMru.push_front( std::make_pair( aQuery,
+                                               std::make_pair( time, aResult ) ) );
+
+        if( it != m_cache.end() )
+        {
+            m_cacheMru.erase( it->second );
+            m_cache.erase( it );
+        }
+
+        m_cache[aQuery] = m_cacheMru.begin();
+
+        if( m_cache.size() > m_maxSize )
+        {
+            auto last = m_cacheMru.end();
+            last--;
+            m_cache.erase( last->first );
+            m_cacheMru.pop_back();
+        }
+    }
+
+    bool Get( const std::string& aQuery, DATABASE_CONNECTION::ROW& aResult )
+    {
+        auto it = m_cache.find( aQuery );
+
+        if( it == m_cache.end() )
+            return false;
+
+        time_t time = std::chrono::system_clock::to_time_t( std::chrono::system_clock::now() );
+
+        if( time - it->second->second.first > m_maxAge )
+        {
+            m_cacheMru.erase( it->second );
+            m_cache.erase( it );
+            return false;
+        }
+
+        m_cacheMru.splice( m_cacheMru.begin(), m_cacheMru, it->second );
+
+        aResult = it->second->second.second;
+        return true;
+    }
+
+    void SetMaxSize( size_t aMaxSize ) { m_maxSize = aMaxSize; }
+    void SetMaxAge( time_t aMaxAge ) { m_maxAge = aMaxAge; }
+
+private:
+    size_t m_maxSize;
+    time_t m_maxAge;
+    std::list<CACHE_ENTRY> m_cacheMru;
+    std::unordered_map<std::string, std::list<CACHE_ENTRY>::iterator> m_cache;
+};
+
+#endif //KICAD_DATABASE_CACHE_H
diff --git a/include/database/database_connection.h b/include/database/database_connection.h
index 387777d275..73e625dfba 100644
--- a/include/database/database_connection.h
+++ b/include/database/database_connection.h
@@ -34,6 +34,8 @@ namespace nanodbc
     class connection;
 }
 
+class DATABASE_CACHE;
+
 
 class DATABASE_CONNECTION
 {
@@ -52,6 +54,8 @@ public:
 
     ~DATABASE_CONNECTION();
 
+    void SetCacheParams( int aMaxSize, int aMaxAge );
+
     bool Connect();
 
     bool Disconnect();
@@ -104,6 +108,8 @@ private:
     long m_timeout;
 
     char m_quoteChar;
+
+    std::unique_ptr<DATABASE_CACHE> m_cache;
 };
 
 #endif //KICAD_DATABASE_CONNECTION_H
diff --git a/include/database/database_lib_settings.h b/include/database/database_lib_settings.h
index dbb994914e..579a34a359 100644
--- a/include/database/database_lib_settings.h
+++ b/include/database/database_lib_settings.h
@@ -76,6 +76,13 @@ struct DATABASE_LIB_TABLE
 };
 
 
+struct DATABASE_CACHE_SETTINGS
+{
+    int max_size;    ///< Maximum number of single-row results to cache
+    int max_age;     ///< Max age of cached rows before they expire, in seconds
+};
+
+
 class DATABASE_LIB_SETTINGS : public JSON_SETTINGS
 {
 public:
@@ -87,6 +94,8 @@ public:
 
     std::vector<DATABASE_LIB_TABLE> m_Tables;
 
+    DATABASE_CACHE_SETTINGS m_Cache;
+
 protected:
     wxString getFileExt() const override;
 };