[英]Will this NHibernate query impact performance?
我正在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");
}
我需要“书签”和“标签”表中的数据。 更具体:我需要20个带有相关标签的书签。 我要做的第一件事是从“书签”表中选择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);
我使用提取的原因是因为我想避免N + 1个查询。 这有效,但会产生笛卡尔积。 我已经读过一些文章,您应该避免使用笛卡尔积,但是我真的不知道该如何做。
我还阅读了一些有关为N + 1查询设置批处理大小的信息。 这真的比单个查询快吗?
用户最多可以将5个标签添加到书签。 我每页选择20个书签,因此第二个查询的最坏情况是:5 * 20 = 100行。
当我在“书签和标签”表中有大量数据时,这会影响性能吗? 我应该以其他方式做吗?
这不是笛卡尔积。
〜图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中修复笛卡尔积的示例,请参见我的其他答案之一 。
Query<>.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<int, Tag>
),您可以在其中执行以下操作:
IGrouping<Tag> thisBookmarksTags = tagLookup[bookmarkId];
它将为您提供该书签所需的标签。 这将其分为另一个查询,从而避免了N + 1。
这为您的数据模型和映射做出了许多假设,但我希望它能说明您可以使用的非常简单的优化。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.