在我的 NHibernate / SQLite 项目中提交非常慢

Posted

技术标签:

【中文标题】在我的 NHibernate / SQLite 项目中提交非常慢【英文标题】:Commit is VERY slow in my NHibernate / SQLite project 【发布时间】:2010-03-26 20:54:30 【问题描述】:

我刚刚开始对我的 Fluent NHibernate / SQLite 项目进行一些实际性能测试,当我提交到数据库时遇到了一些严重的延迟。说真的,我的意思是花 20 - 30 秒来提交 30 K 的数据!

随着数据库的增长,这种延迟似乎会变得更糟。当 SQLite DB 文件为空时,提交几乎立即发生,但当它增长到 10 Meg 时,我看到了这些巨大的延迟。

数据库有 16 个表,每个表平均 10 列。

一个可能的问题是我存储了十几个 IList<float> 成员,但它们通常只有 200 个元素长。但这是 Fluent NHibernate 自动映射的最新功能,它将每个浮点数存储在单个表行中,所以这可能是一个潜在的问题。

有关如何追踪此问题的任何建议?我怀疑 SQLite 是罪魁祸首,但也许是 NHibernate?

我没有使用分析器的任何经验,但我正在考虑购买一个。我知道NHibernate Profiler - 对与 SQLite 配合良好的分析器有什么建议吗?

编辑

更多的测试表明,导致速度变慢的不是数据库的大小,而是取决于我启动程序后执行的保存次数。第一次保存的提交时间约为 300 毫秒,到第 50 次保存时超过 1000 毫秒。

我一直保持一个会话打开 - 也许我需要一些明确的刷新逻辑?

另外,我下载了 NHibernate Profiler。它为我的 IList 成员提供了有关“大量个人写入”的警报。警报描述建议打开批处理,但据我所知,SQLite 不支持。

还提到了 Multiquery 支持,所以我将继续阅读。

/编辑

这是保存数据的方法 - 如果您忽略所有错误处理和调试日志记录,它只是一个 SaveOrUpdate 调用和一个 Commit。

    public static void SaveMeasurement(object measurement)
    
        // Get the application's database session
        var session = GetSession();
        using (var transaction = session.BeginTransaction())
        
            session.Save(measurement);
            transaction.Commit();
        
    

【问题讨论】:

您是否正确使用事务?或者您是否有任何代码在添加新记录之前搜索现有记录? @Kevin - 我想是这样,但我刚刚发布了一个代码片段 - 如果您发现任何问题,请告诉我。当我填充一个空数据库时,我看到了延迟 - 没有其他逻辑在寻找重复数据。 nhprof 与 sqlite 配合得很好 【参考方案1】:

您的交易应该围绕整个操作,而不是每次保存。

您还可以从启用 ado.net 批处理中受益: 1000

【讨论】:

我尝试打开批处理,但没有成功。谷歌搜索出现了几个点击,说 SQLite 不支持批处理,除非最近发生了变化。 SQLite 可以肯定地在每个事务中编写多个内容,并且据我所知,多年来一直能够做到这一点。 NHibernate 是否可以利用这一点,我只是不知道。 NHibernate 有一个叫做 MultiQuery 的东西,我简要地看了一下,但现在我回避了,因为它似乎需要手写 SQL。但很高兴知道 SQLite 有这个功能 - 谢谢!【参考方案2】:

我认为使用 session.evict() 仅涵盖症状。您尚未发布 GetSession() 方法,但上面的评论和 session.clear() 打破您的延迟加载的评论让我猜您正在为整个应用程序使用一个会话。 这是非常低效的,并且运行时间越长,应用程序的运行速度就会越慢。另一方面,创建一个新会话非常便宜,并且将为您提供一个干净、快速的会话,只处理您想要的对象。 IMO,您应该考虑声明式事务管理。我个人更喜欢Springs-TX-Management,但也有其他聪明的解决方案不是基于方法的,例如城堡的ActiveRecord。

【讨论】:

你是对的 - 我保持一个会话打开,因为我的应用程序可以在用户浏览旧测量时收集新测量并将它们写入数据库。如果我打开另一个会话只是为了编写新的测量值,SQLite 会告诉我数据库已锁定。有关如何解决此问题的任何建议? @zoidbeck - 根据您的输入,我现在一直保持会话打开以进行延迟加载,并在保存时断开/重新连接(每次保存都使用新会话)。总体而言,这似乎效果更好,但我担心我刚刚将问题转移到另一个会话... :-( 嗨,汤姆,很遗憾,没有万能的解决方案。如果您没有任何问题,为什么要更改它。另一方面,在多用户环境中,您可能会遇到麻烦。在我看来,通常我尽量不使用 NH 持久对象。我正在使用显式客户端对象来将视图与服务器分离。除了 spring.net 之外,您还可以轻松地在服务器上进行无状态调用,并在被调用的方法上直接配置所需的事务。会话管理、多用户、安全性 - 开箱即用。【参考方案3】:

您说您有一个测量对象列表,但您一次保存它们 1 个。如果所有保存都在 1 个事务中,这会很好,但是您将每个保存都包含在一个事务中。无论数据库有多大,这都会损害您的性能。

【讨论】:

感谢您的建议。我将 SaveOrUpdate 更改为 Save,但仍然出现延迟。目前,我只是保存了大约 1000 个测量对象来填充我的数据库——我提到的 IList 是测量类的成员。但是我使用 DefaultCascade.All() 约定在保存测量值时保存 ILists - 也许这隐含地执行 SaveOrUpdate? @Tom 对不起。我不确定。 1.整个批次使用一个事务 2. 使用 save 添加一个新事务,使用一个事务时根本不必使用显式更新 所以,你们说我应该:1) 做一个“BeginTransaction” 2) 对我的一千个测量值做一个“保存” 3) 做一个“提交”?对吗? SaveOrUpdate 不检查未保存的对象是否已保存。为什么会这样?【参考方案4】:

您是否使用自动增量主键?这将导致 NHibernate 在每次插入后执行选择以填充对象的主键属性。我不确定它是如何为 SQLite 做的,但你说随着数据库的增长问题会变得更糟,这可能是根本原因。

您使用分析器的计划是最好的做法。 NHibernate Profiler 非常出色,并且有试用期,您可以使用它来解决此问题。

【讨论】:

是的,我使用自动增量整数作为主键。切换到 GUID 键可能会消除此问题? 也许吧。但如果不对应用程序进行分析,我不会采取任何行动。首先。【参考方案5】:

Nhibernate 永远不会很快。

一些旧版本的 nhibernate 会主动关闭与数据库的会话。这会导致它打开/关闭 sqlite 数据库很多(这很慢)。我相信较新的版本包括一个选项,可以使休眠会话保持更长的时间。如果您使用的是内存数据库,它会导致所有内容在关闭后立即丢失。

Problem using SQLite :memory: with NHibernate

【讨论】:

我一直保持会话打开,所以不要认为这是一个问题。到目前为止,速度还不是问题。如果您像我一样一次存储数百个,似乎基本值类型的 IList 无法有效保存。【参考方案6】:

编辑 在阅读了 zoidbeck 的回答后,我放弃了这种方法。请参阅我对他的回答的评论。 /编辑

在谷歌搜索之后,我发现一些帖子暗示 NHibernate 缓存在某些情况下实际上可以大大减慢速度。显然,与实际提交相比,NH 可以花费更多时间在缓存中查找内容。

我尝试在每次 Commit 后进行 session.Clear,但这会破坏延迟加载。

解决方案(目前)是调用:

session.Evict(measurement);

在每次提交之后,从缓存中删除测量值。现在,无论数据库有多大,我的提交都需要大致相同的时间(大约 800 毫秒,对于 30 K 的数据)。

问题的根源似乎是我的IList<float> 成员,它们生成了数百个 SQL 插入。批处理可能会解决这个问题,但是,唉,只有 SQL Server 支持,SQLite 不支持。

在某些时候,我将不得不对此进行优化 - 也许通过将它们存储为 BLOB。但这是另一天的问题。

【讨论】:

以上是关于在我的 NHibernate / SQLite 项目中提交非常慢的主要内容,如果未能解决你的问题,请参考以下文章

NHibernate 与 C#-Sqlite 在 > .NET 2.0 中没有 DbConnection

Fluent NHibernate and Mysql,SQLite

NHibernate、SQLite 和 Cyrillic 字符:区分大小写和回退查询

TransactionScope 与 SQLite 内存数据库和 NHibernate

使用 SQLite“没有这样的表”测试 NHibernate - 生成模式

通过(Fluent)NHibernate添加到SQLite DB时,DateTime不正确