当数据库被锁定时,SQLiteConnection.BeginTransaction会重试吗?

5

我正在使用SQLite(System.Data.SQLite.dll)在一个多线程的Windows桌面程序中。我使用以下代码:

using (var cn = new SQLiteConnection(connectionString))
{
    cn.Open();
    using (var tx = cn.BeginTransaction())
    {
          // do some work here

         tx.Commit();
    }
}

我正在使用8个并发线程同时向数据库写入数据,这会对程序进行压力测试。有时我会在Visual Studio的输出窗口看到以下信息:

SQLite error (517): statement aborts at 1: [BEGIN IMMEDIATE] database is locked 

但是,根本没有抛出任何异常,一切都按预期工作。所以我猜测当SQLConnection.BeginTrasaction()接收到SQLite错误(517)数据库被锁定时,它会进行重试。

我猜测正确吗?

如果是的话,SQLConnection.BeginTransaction重试多少次才会抛出异常?这个可以配置吗?

2个回答

3

这由 BUSY TIMEOUT处理:

PRAGMA busy_timeout;
PRAGMA busy_timeout = 毫秒数;

查询或更改繁忙超时设置。此Pragma是sqlite3_busy_timeout() C语言接口的替代品,适用于不提供对sqlite3_busy_timeout()直接访问的语言绑定。

每个数据库连接只能有一个繁忙处理程序。此PRAGMA设置进程的繁忙处理程序,可能会覆盖任何先前设置的繁忙处理程序。

SQLite.dll中,默认情况下将超时间隔定义为30秒。结果发现无法更改,只能通过调用不安全的C函数来更改。

SQLite C Interface

int sqlite3_busy_timeout(sqlite3*, int ms);

使用小于或等于零的参数调用此例程将关闭所有繁忙处理程序。


1
谢谢,那解释得很清楚。 - Jesús López
忙时超时属性(BusyTimeout)发生了什么?它会出现在下一个 System.Data.SQLite 版本中吗?PRAGMA busy_timeout 在我的程序中返回 0。这里发生了什么?System.Data.SQLite 是否使用 sqlite3_busy_handler https://www.sqlite.org/c3ref/busy_handler.html? - Jesús López
结果发现它并没有在Sqlite.dll中声明。它在另一个开源库中。似乎你根本不能明确地设置它 :/ - Yuval Itzchakov

2

我最终在System.Data.SQLite.dll的源代码中找到了它。

SQLite3.Prepare方法(在执行任何命令之前调用)其中包含以下代码:

while ((n == SQLiteErrorCode.Schema || n == SQLiteErrorCode.Locked || n == SQLiteErrorCode.Busy) && retries < 3)
{
  try
  ...

因此,System.Data.SQLite在抛出异常之前会重试任何命令最多3次。
编辑:
SQLite3.Prepare方法有一个有趣的自解释代码片段。
  else if (n == SQLiteErrorCode.Locked || n == SQLiteErrorCode.Busy) // Locked -- delay a small amount before retrying
  {
    // Keep trying
    if (rnd == null) // First time we've encountered the lock
      rnd = new Random();

    // If we've exceeded the command's timeout, give up and throw an error
    if ((uint)Environment.TickCount - starttick > timeoutMS)
    {
      throw new SQLiteException(n, GetLastError());
    }
    else
    {
      // Otherwise sleep for a random amount of time up to 150ms
      System.Threading.Thread.Sleep(rnd.Next(1, 150));
    }
  }

请注意,锁定和繁忙错误不会增加重试计数器,因此Prepare方法将一直重试,直到命令超时过期。
我创建了一个工单,要求最大重试次数可配置: SQLite3.Prepare的可配置重试次数和休眠时间

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