确保每个线程仅一个SQLite连接

问题描述

我正在将Microsoft.Data.sqlite.CoresqlitePCLRaw.lib.e_sqlcipher一起使用来访问加密的sqlite数据库。我需要解决一些挑战:

  • 我必须确保Microsoft.Data.sqlite.sqliteConnection的同一实例不在多个线程之间共享(sqlite连接不是线程安全的);
  • 打开sqlCipher加密数据库非常昂贵,因此我必须将发生的次数限制为最少。

我想出了解决方案,但是我不确定该解决方案的可靠性如何,所以我希望有人可以对此提出一些漏洞。

解决方法

这是我的解决方案,请让我知道我是否搞砸了:

public class SqliteConnectionPool
{
    private readonly object lockObject_ = new object();

    private readonly string connectionString_;
    private readonly Dictionary<Thread,SqliteConnection> pool_;

    public SqliteConnectionPool(string connectionString)
    {
        new SqliteConnectionStringBuilder
        {
            ConnectionString = connectionString // throws if connection string is invalid
        };

        connectionString_ = connectionString;
        pool_ = new Dictionary<Thread,SqliteConnection>();
    }

    public SqliteConnection GetConnection()
    {
        lock (lockObject_)
        {
            Thread currentThread = Thread.CurrentThread;

            // If this thread owns a connection,just retrieve it.
            if (pool_.ContainsKey(currentThread))
            {
                return pool_[currentThread];
            }

            // Looking for a thread that doesn't need its connection anymore.
            (Thread inactiveThread,SqliteConnection availableConnection) = pool_.Where(p => p.Key.ThreadState != ThreadState.Running)
                .Select(p => (p.Key,p.Value))
                .FirstOrDefault();

            // If all existing connections are being used,create a new one.
            if (availableConnection is null)
            {
                var connection = new SqliteConnection(connectionString_);
                pool_[currentThread] = connection;
                return connection;
            }

            // Otherwise,just use the existing free connection.
            pool_.Remove(inactiveThread);
            pool_[currentThread] = availableConnection;
            return availableConnection;
        }
    }
}
,

我面临着与您相同的认识,并且必须做类似的事情。我认为您的代码没有问题。

我将尝试使用 ConcurrentDictionary 进行一些优化。它应该能让我避免在更新时锁定读者。

如果您继续使用 Dictionary,您可能希望将 ContainsKey() 调用更改为 TryGetValue(),因为文档表明如果使用不存在的键可能会更有效(我们经常会看到新线程?)

如果其他人开车经过,这是我对这个问题的背景研究: Microsoft.Data.Sqlite 派生自 ADO.NET 对象,例如根据设计,DbConnection 不是线程安全的。 ADO.NET 的设计者在高性能的祭坛上牺牲了线程安全。出于这个原因,任何使用从 ADO.NET 派生的任何代码的任何代码最终都需要进入这个兔子洞,以确保不会发生真正奇怪的事情。

至于 Microsoft.Data.Sqlite,我的问题是 SqliteCommand.Dispose,它在内部遇到空值并崩溃。这是框架在不同线程上有大量并行调用的时候。

您可能还注意到 sqlite 本身具有多线程设置,并相信这个兔子洞适合您。不幸的是,摆弄这些设置,尽管它们可能有益,但不会改变 ADO.NET 对象设计,因此只要您使用 Microsoft.Data.Sqlite 访问 sqlite,问题就会一直存在。

欢迎发布您的代码!希望它能够成功地投入生产:-)

/尼克

发帖后补充: 保存对 Thread 和 SqliteConnection 的引用的 Dictionary 将对资源产生影响,因为只要引用存在,它就会停止垃圾收集器回收它们的资源。 SqliteConnections 也是如此。 可能不是一个真正的问题,但人们可能还想考虑某种清理算法,在一段时间后删除陈旧的 Thread 引用。如果将 SqliteConnections 移动并单独存储,仍然可以重用。

/尼克