C# - 如何检测SQ​​Lite DB是否被锁定?

时间:2017-11-13 03:45:36

标签: c# database multithreading sqlite

我正在使用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)如何检测数据库是否被锁定错误发生。提前谢谢。

2 个答案:

答案 0 :(得分:0)

如果您收到错误代码5(忙),可以使用即时交易限制此操作。如果您能够立即开始交易,SQLite会保证您在提交之前不会收到繁忙的错误。

另请注意,SQLite没有行级锁定。整个数据库都被锁定。使用WAL日记,您可以使用一个作者和多个读者。使用其他日记方法,您可以使用一个编写器或多个读取器,但不能同时使用它们。

关于' SQLITE_BUSY'

的SQLite文档

答案 1 :(得分:0)

为了检测 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)。