【问题标题】:Will this NHibernate query impact performance?这个 NHibernate 查询会影响性能吗?
【发布时间】:2013-11-07 18:53:47
【问题描述】:

我在 ASP.NET MVC 中创建一个网站并使用 NHibernate 作为 ORM。我的数据库中有以下表格:

  • 书签
  • 标签书签(连接表)
  • 标签

映射:

    public BookmarkMap()
    {
        Table("Bookmarks");
        Id(x => x.Id).Column("Id").GeneratedBy.Identity();
        Map(x => x.Title);
        Map(x => x.Link);
        Map(x => x.DateCreated);
        Map(x => x.DateModified);
        References(x => x.User, "UserId");
        HasManyToMany(x => x.Tags).AsSet().Cascade.None().Table("TagsBookmarks").ParentKeyColumn("BookmarkId")
        .ChildKeyColumn("TagId");
    }

    public TagMap()
    {
        Table("Tags");
        Id(x => x.Id).Column("Id").GeneratedBy.Identity();
        Map(x => x.Title);
        Map(x => x.Description);
        Map(x => x.DateCreated);
        Map(x => x.DateModified);
        References(x => x.User, "UserId");
        HasManyToMany(x => x.Bookmarks).AsSet().Cascade.None().Inverse().Table("TagsBookmarks").ParentKeyColumn("TagId")
        .ChildKeyColumn("BookmarkId");
    }

我需要 Bookmarks 和 Tags 表中的数据。更具体地说:我需要 20 个带有相关标签的书签。我要做的第一件事是从 Bookmarks 表中选择 20 个书签 ID。我这样做是因为分页不适用于我在第二个查询中得到的笛卡尔积。

第一个查询:

IEnumerable<int> bookmarkIds = (from b in SessionFactory.GetCurrentSession().Query<Bookmark>()
                                where b.User.Username == username
                                orderby b.DateCreated descending
                                select b.Id).Skip((page - 1) * pageSize).Take(pageSize).ToList<int>();

然后我为这些 id 选择书签。

第二次查询:

IEnumerable<Bookmark> bookmarks = (from b in SessionFactory.GetCurrentSession().Query<Bookmark>().Fetch(t => t.Tags)
                                   where b.User.Username == username && bookmarkIds.Contains(b.Id)
                                   orderby b.DateCreated descending
                                   select b);

我使用 fetch 的原因是因为我想避免 N+1 查询。这可行,但会产生笛卡尔积。我在一些帖子中读到你应该避免笛卡尔积,但我真的不知道如何在我的情况下做到这一点。

我还阅读了有关为 N+1 查询设置批量大小的内容。这真的比单个查询快吗?

用户最多可以向书签添加 5 个标签。我每页选择 20 个书签,因此第二个查询的最坏情况是:5 * 20 = 100 行。

当书签和标签表中有大量数据时,这会影响性能吗?我应该这样做吗?

【问题讨论】:

  • 你想要达到的最终结果是什么?
  • 带有书签及其相关标签的页面。已添加到问题中。

标签: c# sql nhibernate


【解决方案1】:

这不是笛卡尔积。

~图A~

Bookmarks -> Tags -> Tag

笛卡尔积是两个不同集合的所有可能组合。例如,假设我们有三个表:Customer、CustomerAddress 和 CustomerEmail。客户有很多地址,他们也有很多电子邮件地址。

~图B~

Customers -> Addresses
          -> Emails

如果你写了一个类似...的查询

select *
from
    Customer c
    left outer join CustomerAddress a
        on c.Id = a.Customer_id
    left outer join CustomerEmail e
        on c.Id = e.Customer_id
where c.Id = 12345

...并且该客户有 5 个地址和 5 个电子邮件地址,您最终会返回 5 * 5 = 25 行。您可以看到为什么这对性能不利。这是不必要的数据。了解客户地址和电子邮件地址的所有可能组合对我们没有任何用处。

通过您的查询,您不会返回任何不必要的行。结果集中的每一行直接对应于您感兴趣的一个表中的一行,反之亦然。没有乘法。相反,你有TagsBookmarksCount + BookmarksThatDontHaveTagsCount

查找笛卡尔积的关键位置是当您的查询分支为两个独立的不相关集合时。如果您只是越来越深入地挖掘单个子集合链,如 图 A 所示,则不存在笛卡尔积。您的查询返回的行数将受到该最深集合返回的行数的限制。一旦你分支到一边,这样你现在在查询中有两个并行的并排集合,如 图 B 所示,那么你就有一个笛卡尔积,结果将是不必要的倍增。

要修复笛卡尔积,请将查询拆分为多个查询,这样行数是相加的,而不是相乘的。使用 NHibernate 的 Future 方法,您可以将这些单独的查询批处理在一起,因此您仍然只需一次往返数据库。有关如何在 NHibernate 中修复笛卡尔积的示例,请参阅 one of my other answers

【讨论】:

  • 所以如果我理解正确,我没有笛卡尔积?如果是这样,您提供的链接是否有助于解决此问题?非常感谢您的回答!
  • 正确。您没有笛卡尔积。在未来的某个时候,如果您最终需要处理一个笛卡尔积,该链接提供了一个如何修复它的示例。
  • 好的。我有最后一个问题 :) 我可能过于担心,但我的查询会表现良好还是应该以不同的方式完成?
  • 我认为这是一个非常好的查询。渴望获取,分页,没有笛卡尔刺激。我看到的唯一可能改进的是您如何两次往返数据库。您可以尝试将其组合到一个查询中。不要使用ToList() 评估bookmarkIds,而是将其保留为IQueryable应该 转换为 SQL 端的子查询。这将是一个更复杂的查询,并且额外的复杂性可能会使数据库更难有效地执行它。您只需测试该方法的两个不同版本,看看哪个更快。
【解决方案2】:

Query&lt;&gt;.Fetch() 旨在确保进行急切加载,并且当它是一对多关系时,就像这似乎是(即如果Bookmark.Tags 是一个集合),那么您将采用两种方式关于这一点大致相当。如果Tags 是延迟加载的并且很少访问,那么不获取它可能是最好的方法(就像在您的第一个查询中一样),因为您不会总是经常访问标签。这取决于用例。

另一方面,如果您知道您将始终获取所有标签,则将其分解为另一个查询可能更有意义,这次是在 Tags 类型/表是什么上,并且查找它们而不是使用 NHibernate 关系来完成这项工作。

如果Tag 具有书签的外键,例如BookmarkId,则 ToLookup 在这种情况下会很有用:

var tagLookup = (from t in SessionFactory.GetCurrentSession().Query<Tag>()
                 // limit query appropriately for all the bookmarks you need
                 // this should be done once, in this optimization
                 select new {key=t.BookmarkId, value=t} )
                 .ToLookup(x=>x.key, x=>x.value);

会给你一个查找 (ILookup&lt;int, Tag&gt;),你可以在其中执行以下操作:

IGrouping<Tag> thisBookmarksTags = tagLookup[bookmarkId];

这将为您提供该书签所需的标签。这会将其分离到另一个查询中,从而避免 N+1。

这对您的数据模型和映射做了很多假设,但我希望它说明了您可以使用的非常直接的优化。

【讨论】:

  • 感谢您的回答。但是我在书签和标签之间有多对多的关系。这是这样映射的:HasManyToMany(x => x.Tags) 所以我在 Tag 中没有书签 ID。
  • 所以你有类似 BookmarkTags 关系表的东西?只需查询它,以相同的方式构建查找。
  • 所以我需要映射联结表?
  • 这是一种方法,如果您想构建查找以针对您的情况进行优化。请记住,我只是建议一种优化方法,我不知道您情况中的其他因素。也就是说,我定期映射关系表,因为从这个角度来看它们很有用。如果您将表视为关系图,则可以将使用 Fetch 的 Eager fetch 想象为一种在给定查询或一组查询中增强您需要的图片部分的方法。
  • 我还确保映射任何关系的 id,这样我就不会处于必须加载该实体以确定另一个关系的情况。使用 NHibernate,这将影响您的更新策略,所以我想这就是我对如何配置事物的全部看法。
猜你喜欢
  • 2011-04-09
  • 1970-01-01
  • 1970-01-01
  • 2014-07-13
  • 2010-10-06
  • 1970-01-01
  • 1970-01-01
  • 2011-02-17
  • 1970-01-01
相关资源
最近更新 更多