从 NHibernate 执行的查询很慢,但从 ADO.NET 执行的查询很快。

12

我在我的MVC应用程序中有一个查询,需要大约20秒才能完成(使用NHibernate 3.1)。但是当我在Management Studio上手动执行这个查询时,它只需要0秒。

我在SO上看到了类似的问题,所以我进一步测试了一下。

我使用Sql Server Profiler拦截了查询,并在我的应用程序中使用ADO.NET执行了该查询。

从Profiler获得的查询类似于:“exec sp_executesql N'select....'”

我的ADO.NET代码:

SqlConnection conn = (SqlConnection) NHibernateManager.Current.Connection;

var query = @"<query from profiler...>";
var cmd = new SqlCommand(query, conn);

SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
return RedirectToAction("Index");

这个查询也非常快,执行起来不需要时间。

此外,在分析器上我看到了一些奇怪的东西。当从NH执行查询时,统计信息如下:

读取:281702 写入:0

而ADO.NET版本则是:

读取:333 写入:0

有人有任何线索吗?我可以提供哪些信息来帮助诊断问题?

我想可能与某些连接设置有关,但ADO.NET版本正在使用与NHibernate相同的连接。

提前感谢。

更新:

我正在使用NHibernate LINQ。查询很大,但是它是一个分页查询,只获取10条记录。

传递给“exec sp_executesql”的参数为:

@p0 int,@p1 datetime,@p2 datetime,@p3 bit,@p4 int,@p5 int

@p0=10,@p1='2009-12-01 00:00:00',@p2='2009-12-31 23:59:59',@p3=0,@p4=1,@p5=0


尝试使用nhprof,可能NH之后会发出其他查询,可能是N+1问题。 - Chris Chilvers
3
很可能是参数嗅探。请参阅“在应用程序中缓慢,在SSMS中快速?”(http://www.sommarskog.se/query-plan-mysteries.html)。 - Martin Smith
@Martin:我最初也是这么想的,但我的 ADO.NET 测试也从 Web 应用程序运行了相同的查询,包括相同的参数,并且速度很快。 - psousa
@psousa - 捕获两者的执行计划。相信您会发现由于某种原因它们正在使用不同的计划。 - Martin Smith
@Martin:我已经完成了,但它们非常庞大。有没有特别需要我注意的地方? - psousa
显示剩余5条评论
4个回答

8

我曾使用ADO.NET和NHibernate来制定不同的查询计划,但在NH版本上遇到了参数嗅探的影响。为什么呢?因为我之前用一个很小的日期区间做了一次查询,这个查询的存储计划就被针对性地优化了。

后来,当我用一个很大的日期区间进行查询时,系统就会使用之前优化过的存储计划,导致查询结果和时间都非常慢。

我确认这确实是问题所在,只需要简单地:

DBCC FREEPROCCACHE -- clears the query-plan cache

我发现了两种解决方法,都可以让我的查询快速执行:

  • 使用NH拦截器,在查询中注入"option(recompile)"选项。
  • 在我的NH Linq表达式中添加一个虚拟谓词,例如:query = query.Where(true)。尤其是当期望结果集很小(按日期间隔计算)时,这样可以创建两个不同的查询计划,一个用于大数据集,另一个用于小数据集。

我尝试了这两种方法,都有效,但最终选择了第二种方法。虽然有点小技巧,但在我的情况下效果非常好,因为数据按日期分布均匀。


3
我想指出,“DBCC FREEPROCCACHE”会清除整个缓存。在系统有很多活动(如生产环境)的情况下,这可能超出了您的预期。 - mcfea
这个答案提供了一个使用拦截器在查询结尾添加 OPTION OPTIMIZE FOR @p UNKNOWN 的示例。 - vgru
与其调整查询,优化索引也可以解决这个问题。在我看来,调整索引应该是首选的方法。当查询存在不足的索引(或缺乏索引)时,参数嗅探会导致问题出现。但有时为所有查询建立合适的索引很难。 - Frédéric

7
我遇到了和楼主一样的问题。我尝试了@psousa的建议,加入了“option(recompile)”来提高性能。但最后我发现,仅仅是更新SQL Server上的统计信息就解决了我的问题。
update statistics tablename;

最终我撤销了注入"option(recompile)"的代码。我意识到这可能不适用于每个人,但想分享一下因为它是我的问题原因。


@SkipHarris 到目前为止一切顺利。这对我产生了巨大的改善 _sessionFactory.GetCurrentSession().CreateSQLQuery("update statistics [tablename];").ExecuteUpdate(); - onefootswill

2

注意被提供给sp_executesql存储过程的参数。如果这些参数是作为nvarchar(N'value')提供的,并且它们所引用的列是varchar类型,那么SQL Server将使用非常低效的查询计划。这是我遇到的所有表现出这些症状(在应用程序中缓慢,在SSMS中快速)的性能问题的根本原因。


这是一个不错的提示,但很遗憾不是。参数是:@p0 int,@p1 datetime,@p2 datetime,@p3 bit,@p4 int,@p5 int',@p0=10,@p1='2009-12-01 00:00:00',@p2='2009-12-31 23:59:59',@p3=0,@p4=1,@p5=0" - psousa
此外,在我的第二个测试中,应用程序很快,但是直接使用ADO.NET而不是NH。 - psousa

0

你没有指定查询或其结果集的大小,但使用nHibernate获取大量实体存在问题。
基本上,“填充”对象所需的时间是导致延迟的原因。
你可以尝试打开反射优化器或使用IStatelessSession。
查看我在这里得到的一些建议。


实际上不是这样的,因为我比较的时间来自 Sql Server Profiler(列“duration”),甚至在数据被填充之前。 - psousa

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