【问题标题】:C# - How to detect if SQLite DB is locked?C# - 如何检测 SQLite DB 是否被锁定?
【发布时间】:2017-11-13 03:45:36
【问题描述】:

我正在开发一个使用 SQLite 的多线程 C# 程序。我遇到了一个问题,有时运行 SQLiteCommand.ExecuteNonQuery() 来更新某些行会抱怨“SQLite 错误 (5):数据库已锁定”。我知道发生这种情况是因为在进行插入或更新时数据库被锁定,所以如果另一个修改数据库的查询出现,第二个查询将导致这个数据库被锁定错误。所以我正在尝试对其实施解决方法,但我不确定我应该如何去做。

我试图做到这一点,以便如果引发数据库锁定错误,那么程序会稍等片刻,然后再试一次,直到它工作为止。但不知何故,没有任何异常被捕获,并且代码只是退出了 try-catch 块,即使数据库锁定消息仍然打印在调试输出中。我不完全确定 SQL 查询是被拒绝还是被接受。

我也尝试过使用 TransactionScope,从那以后我没有遇到过数据库被锁定的事情,但是由于问题的随机性,我不能 100% 确定 TransactionScope 是否真的解决了问题,或者它是否只是在一定程度上可以,或者如果没有,到目前为止我很幸运。

SQLiteConnection connection = new SQLiteConnection("Data Source=DB.db;Version=3;");
connection.Open();
SQLiteCommand command = connection.CreateCommand();
command.commandText = inputQuery;
try
{
  command.executeNonQuery();
}
catch (SQLiteException sqle)
{
  Debug.WriteLine("Database error: " + e.Message);
  return false;
}
catch (Exception e)
{
  Debug.WriteLine("Database error: " + e.Message);
  return false;
}
finally
{
  connection.Close();
}

所以我真的很想有人帮我找出 1)如何消除数据库被锁定的问题或 2)如何检测数据库是否被锁定错误发生。提前致谢。

【问题讨论】:

  • 我建议使用生产者-消费者模式(使用合适的锁、同步原语或异步模式)在单个线程中执行所有 SQLite 操作。
  • 谢谢。为了完全摆脱数据库锁定,这似乎是最安全的方法。

标签: c# database multithreading sqlite


【解决方案1】:

如果您收到错误代码 5(忙碌),您可以通过使用即时交易来限制这一点。如果您能够立即开始事务,SQLite 保证在您提交之前不会收到繁忙错误。

还要注意 SQLite 没有行级锁定。整个数据库被锁定。使用 WAL 日志,您可以一个作者和多个读者。使用其他日记方法,您可以有一个作者或多个读者,但不能同时拥有。

关于“SQLITE_BUSY”的 SQLite 文档

【讨论】:

  • 谢谢。什么是即时交易?顺便说一句,目前我正在使用 TransactionScope,因为据说 SQLiteConnection.BeginTransaction() 已被弃用,但我正在考虑改回 BeginTransaction,因为它似乎是使用更广泛的一个。
  • Nvm,我发现 BEGIN IMMEDIATE。这肯定会对我有所帮助,因为我可以为 BEGIN IMMEDIATE 部分实现持续尝试算法。但是我不确定如何从 C# 使用 BEGIN IMMEDIATE。
【解决方案2】:

为了检测 Sqlite DB 是否被锁定,我使用了 Determine whether SQLite database is locked 的方法 这里的想法是尝试获取锁并立即释放它,如果它没有失败,那么 DB 没有被锁定。 以下 C# 代码对我有用:

public bool IsDatabaseLocked(string dbPath)
    {
        bool locked = true;
        SQLiteConnection connection = new SQLiteConnection($"Data Source={dbPath};Version=3;");
        connection.Open();
        
        try
        {
            SQLiteCommand beginCommand = connection.CreateCommand();
            beginCommand.CommandText = "BEGIN EXCLUSIVE"; // tries to acquire the lock
            // CommandTimeout is set to 0 to get error immediately if DB is locked 
            // otherwise it will wait for 30 sec by default
            beginCommand.CommandTimeout = 0; 
            beginCommand.ExecuteNonQuery();

            SQLiteCommand commitCommand = connection.CreateCommand();
            commitCommand.CommandText = "COMMIT"; // releases the lock immediately
            commitCommand.ExecuteNonQuery();
            locked = false;
        }
        catch(SQLiteException)
        {
            // database is locked error
        }
        finally
        {
            connection.Close();
        }

        return locked;
    }

然后,当您确定数据库是否被锁定时,您可以等待它被解锁:

public async Task WaitForDbToBeUnlocked(string dbPath, CancellationToken token)
    {
        while (IsDatabaseLocked(dbPath))
        {
            await Task.Delay(TimeSpan.FromSeconds(1), token);
        }
    }

或在运行查询之前向其他线程发送取消消息(例如通过 CancellationTokenSource)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-26
    • 2012-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-02
    • 2010-10-06
    • 1970-01-01
    相关资源
    最近更新 更多