【问题标题】:sqlite returns SQLITE_BUSY in WAL modesqlite 在 WAL 模式下返回 SQLITE_BUSY
【发布时间】:2012-08-13 19:43:00
【问题描述】:

我有一个使用 sqlite 数据库的 Web 应用程序。 我的 sqlite 版本是官方 windows 二进制发行版的最新版本 - 3.7.13。

问题是在数据库负载很重的情况下,sqlite API 函数(例如 sqlite3_step)返回 SQLITE_BUSY。

我在初始化连接时传递了以下编译指示:

journal_mode = WAL
page_size = 4096
synchronous = FULL
foreign_keys = on

数据库是单文件数据库。我正在使用 Mono 2.10.8 和它提供的 Mono.Data.Sqlite 程序集来访问数据库。

我正在使用 50 个并行线程对其进行测试,这些线程分别向我的应用程序发送 50 个后续 http 请求。在每次请求时,都会对数据库进行一些读取和写入。每组 IO 操作都在事务内部执行。

在第 400 到 700 个请求之前一切顺利。在这个(随机)时刻,API 函数开始永久返回 SQLITE_BUSY(更准确地说 - 直到达到重试限制)。

据我所知,WAL 模式透明地支持并行读写。我猜这可能是因为在执行检查点操作时尝试读取数据库。但即使关闭自动检查点,情况仍然相同。

在这种情况下会出现什么问题? 如何正确处理大量并行数据库 IO?

附言

假设每个请求只有一个连接。 我使用配置了 WebSessionContext 的 nhibernate。

我像这样初始化我的 NHibernate 会话:

ISession session = null;

//factory variable is session factory
if (CurrentSessionContext.HasBind(factory))
{
  session = factory.GetCurrentSession();
  if (session == null)
    CurrentSessionContext.Unbind(factory);
}

if (session == null)
{
  session = factory.OpenSession();
  CurrentSessionContext.Bind(session);
}

return session;

在 HttpApplication.EndRequest 我这样发布它:

//factory variable is session factory
if (CurrentSessionContext.HasBind(factory))
{
  try
  {
    CurrentSessionContext.Unbind(factory)
      .Dispose();
  }
  catch (Exception ee)
  {
    Logr.Error("Error uninitializing session", ee);
  }
}

据我所知,每个请求生命周期应该只有一个连接。在处理请求时,代码按顺序执行(ASP.NET MVC 3)。所以看起来这里不可能有任何并发​​性。我可以断定在这种情况下没有共享连接吗?

【问题讨论】:

  • 是为每个请求打开不同的连接,还是所有请求共享同一个连接?另外,您能否添加一些相关的代码,尤其是线程与数据库交互的部分?
  • 您对 WAL 模式的假设是错误的。 sqlite.org/wal.html 说:“由于只有一个 WAL 文件,因此一次只能有一个写入器。”对于写入负载较重的数据库,请使用 PostgreSQL 或 MySQL。

标签: multithreading sqlite transactions mono


【解决方案1】:

我不清楚请求线程是否共享相同的连接。如果他们不这样做,那么您应该不会遇到这些问题。

假设您确实在多个线程之间共享连接对象,您应该使用一些锁定机制作为SqliteConnection isn't thread-safe(一个旧帖子,但作为 Mono 的一部分维护的 SQLite 库是从 System.Data.SQLite 演变而来的在http://sqlite.phxsoftware.com)。

所以假设您不使用 SqliteConnection 对象进行锁定,您可以尝试一下吗?完成此操作的简单方法如下所示:

static readonly object _locker = new object();

public void ProcessRequest()
{
    lock (_locker) {
        using (IDbCommand dbcmd = conn.CreateCommand()) {
            string sql = "INSERT INTO foo VALUES ('bar')";
            dbcmd.CommandText = sql;
            dbcmd.ExecuteNonQuery();
        }
    }
}

但是,您可以选择为每个线程打开不同的连接,以确保您不再遇到 SQLite 库的线程问题。

编辑

跟进您发布的代码,您是否在提交事务后关闭会话?如果你不使用一些 ITransaction,你会刷新并关闭会话吗?我在问,因为我在你的代码中没有看到它,我看到它在 https://stackoverflow.com/a/43567/610650 中提到过

我也看到http://nhibernate.info/doc/nh/en/index.html#session-configuration提到它:

还请注意,您可以调用 NHibernateHelper.GetCurrentSession();作为 你喜欢多少次,你总会得到当前的ISession 这个 HTTP 请求。您必须确保 ISession 在之后关闭 您的工作单元在 Application_EndRequest 事件中完成 在您的应用程序类或 HTTP 之前的 HttpModule 中的处理程序 响应已发送。

【讨论】:

  • 感谢您的回答。我添加了一些信息来回答您的问题。请看一下
  • 我完全按照 nhforge 的报价。与数据库的每个对话都被包装到工作单元中,即每个查询块都在事务中执行,没有事务就不会执行任何查询。我的会话在 EndRequest 发布。作为CL。在 cmets 中提到了这个问题,SQLite 文档说在 WAL 模式下并发写入是不可能的。所以我想我会给你一个赏金。但是如果你知道如何使用 SQLite 实现并发写入(没有垄断锁),请分享这些知识。
  • @ILya:查看您发布的代码,没有 session.Close();在 EndRequest 所以我的问题是你真的打那个电话吗?也是的,SQLite 一次不会有超过 1 个作者,这是你从一开始就建立的,我绝不会试图重新审视它。据我所知,SQLite 可以与多个作者一起正常工作,直到出现太多。什么太多取决于很多因素。但是您的问题似乎暗示问题的出现不是因为一次请求越来越多,而是因为时间过去了越来越多。
  • 我对我的会话进行 Dispose(),所以这部分没问题。是的,我使用的提供程序在重试调用 sqlite3_step 超时后抛出异常。当队列太长时会发生这种情况。所以谢谢。我现在很清楚了。
  • 我给了你一个赏金,但请在你的答案中添加并发写入是不可能的,因此它将是完整的。谢谢。
猜你喜欢
  • 1970-01-01
  • 2013-03-30
  • 2012-05-07
  • 2016-08-26
  • 2011-10-25
  • 2018-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多