C# - 如何检测SQLite数据库是否被锁定?

3
我正在开发一个使用SQLite的多线程C#程序。有时运行SQLiteCommand.ExecuteNonQuery() 更新一些行时会出现“SQLite错误(5):数据库已锁定”的问题。我知道这是因为在插入或更新时数据库被锁定,因此如果另一个修改数据库的查询出现,第二个查询将出现此“数据库已锁定”错误。因此,我正在尝试实现解决方法,但我不确定应该如何做。
我试图使程序在抛出数据库锁定错误时等待一段时间并重试,直到成功。但是某种原因没有捕获异常,代码仍然退出try-catch块,尽管在调试输出中仍然打印了数据库锁定消息。我不确定SQL查询是被拒绝还是被接受。
我还尝试使用TransactionScope,自那时以来就没有出现过“数据库已锁定”的问题,但由于问题的随机性,我无法百分之百确定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操作。 - Dai
谢谢。这似乎是最安全的方法,可以彻底避免数据库锁定。 - HJKIM
2个回答

1
为了检测Sqlite数据库是否被锁定,我使用了确定SQLite数据库是否被锁定的方法。这里的思路是尝试获取锁并立即释放它,如果没有失败,则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)。


0

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

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

SQLite关于“SQLITE_BUSY”的文档


谢谢。什么是即时事务?顺便说一下,目前我正在使用TransactionScope,因为据说SQLiteConnection.BeginTransaction()已经被弃用了,但我正在考虑改回BeginTransaction,因为它似乎是更广泛使用的方法。 - HJKIM
没事了,我找到了 BEGIN IMMEDIATE。这一定会帮助我,因为我可以为 BEGIN IMMEDIATE 部分实现 keep trying 算法。但是我不确定如何在 C# 中使用 BEGIN IMMEDIATE。 - HJKIM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接