2012-08-17 132 views
4

我有一个web应用程序使用sqlite数据库。 我的版本的sqlite是官方windows二进制发行版的最新版本 - 3.7.13。sqlite在WAL模式下返回SQLITE_BUSY

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

我初始化连接时,通过以下编译:

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

的databas是一个文件数据库。我使用Mono 2.10.8和Mono.Data.Sqlite程序集来访问数据库。

我用50个并行线程测试它,每个线程发送50个后续http请求到我的应用程序。在每次请求时,都会对数据库进行一些读写操作。每一组IO操作都在事务内执行。

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

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

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

P.S.

假设每个请求只有一个连接。 我使用配置了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)。所以在这里看起来并不是什么可能。我可以得出结论,在这种情况下没有连接共享吗?

+0

为每个请求打开了一个独特的连接,还是所有请求都共享相同的连接?另外,你可以添加一些相关的代码,尤其是线程与数据库交互的部分吗? – 2012-08-29 10:37:57

+2

你对WAL模式的假设是错误的。 http://www.sqlite.org/wal.html说:“因为只有一个WAL文件,所以一次只能有一个作者。”对于有大量写入负载的数据库,请使用PostgreSQL或MySQL之类的东西。 – 2012-09-02 12:50:10

回答

3

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

假设您确实跨多个线程共享连接对象,则应该使用一些锁定机制作为SqliteConnection isn't thread-safe(旧帖子,但将SQLite库作为从System.Data.SQLite发展而来的Mono的一部分进行维护) 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库更多的线程问题。

编辑

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

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

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

+0

感谢您的回答。我添加了一些信息来回答你的问题。请看看 – ILya 2012-08-29 14:21:06

+0

@ILya我加入了我的答案。 – 2012-08-29 14:46:34

+0

我的确如在nhforge的报价中一样。与数据库的每次对话都被包装到工作单元中,即每个查询块都在事务中执行,并且没有事务执行查询。我的会话在EndRequest发布。作为CL。在对问题的评论中提到,SQLite文档说在WAL模式下并发写入是不可能的。所以,我会给你一个奖励。但是如果你知道如何实现与SQLite的并发写入(没有垄断锁),请阅读这些知识。 – ILya 2012-09-03 08:30:36