视图会降低EF查询性能吗?

3
我正在寻找一些改进实体框架查询性能的技巧,并偶然发现了这篇有用的文章
该文章的作者提到了以下内容:

09 避免使用视图

视图会显著降低LINQ查询的性能。它们的性能很慢,对性能影响很大。因此,在LINQ to Entities中避免使用视图。

我只熟悉数据库中“视图”的这个含义。由于我不理解这个语句,他是指哪些视图?

4
在我看来,EF本身更有可能通过糟糕的SQL和惰性加载引入性能问题,而不是视图所带来的任何问题! - Liath
是可能的,但你需要调查一下Linq实际上是如何处理视图的,而且即使这样,它也可能取决于视图/查询的复杂性。我曾经看到其他ORM在使用视图时会有显著的性能损失,这是因为它们处理视图的方式,但我个人还没有对Linq/EF与视图进行过调查。 - Allan S. Hansen
1
这个问题没有具体的例子是无法回答的。视图仍然可以提供巨大的性能提升。顺便说一下,“有用的文章”现在已经不存在了。 - Gert Arnold
2个回答

4

这取决于具体情况,但很少有显著影响。

假设我们有一个视图,如下所示:

CREATE VIEW TestView
AS
Select A.x, B.y, B.z
FROM A JOIN B on A.id = B.id

并且我们为此创建了一个实体映射。
假设 B.id 已经绑定,因此它是非空的,并且与 A.id 存在外键关系 - 也就是说,每当有一个 B 行时,总是至少有一个对应的 A
现在,如果我们可以做一些像 from t in context.TestView where t.x == 3 这样的事情,而不是 from a in context.A join b in context.B on a.id equals b.id where a.x == 3 select new {a.x, b.y, b.z}
我们可以预期前者会被转换为 SQL 的速度略快,因为它是一个略微更简单的查询(从 Linq 和 SQL 的角度来看)。
我们可以预期后者从 SQL 查询转换为 SQLServer(或其他)内部查询的速度会略快。
我们可以期望该内部查询基本相同,除非出了点问题。因此,在那一点上,我们预计表现将是相同的。
总的来说,它们之间没有太大的区别。如果我必须打赌一个,我会打赌使用视图稍微更快,特别是在第一次调用时,但我不会押很多钱。
现在让我们考虑 (from t in context.TestView select t.z).Distinct(). vs (from b in context.B select b.z).Distinct()
这两个都应该变成一个相当简单的 SELECT DISTINCT z FROM ...
这两个都应该只扫描表 B 的表扫描或索引扫描。
第一个可能不会(查询计划中存在缺陷),但这是令人惊讶的。(对类似视图的快速检查确实发现 SQLServer 忽略了无关的表)。
第一个可能需要更长时间来生成查询计划,因为必须推断出在 A.id 上的连接是无关的。但是,数据库服务器擅长这种事情;它是一组计算机科学和已经进行了数十年研究的问题。
如果我必须打赌一个,我会打赌视图使事情变得非常轻微地变慢,尽管我会更加确信它如此轻微的差异会消失。对于这两种查询的实际测试发现二者之间的差距处于同一范围内(即两个的不同时间重叠)。
在这种情况下,对于从 linq 查询生成的 SQL 的影响将为零(在那一点上,它们实际上是相同的,但名称不同)。
让我们考虑如果我们在该视图上有一个触发器,以便插入或删除执行等效的插入或删除。在这种情况下,我们将从使用一个 SQL 查询中获得略微的收益,而不是两个(或更多),并且更容易确保它在单个事务中发生。因此,在此情况下,视图会稍微增加一些好处。
现在,让我们考虑一个更复杂的视图:
CREATE VIEW Complicated
AS
Select A.x, B.x as y, C.z, COALESCE(D.f, D.g, E.h) as foo
FROM
A JOIN B on A.r = B.f + 2
JOIN C on COALESCE(A.g, B.x) = C.x
JOIN D on D.flag | C.flagMask <> 0
WHERE EXISTS (SELECT null from G where G.x + G.y = A.bar AND G.deleted = 0)
AND A.deleted = 0 AND B.deleted = 0

我们可以在LINQ级别上完成所有这些操作。如果我们这样做,查询生成可能会有点昂贵,尽管这很少是对LINQ查询整体影响最大的部分,尽管编译查询可能会平衡这一点。
我倾向于认为视图是更有效的方法,但如果这是我使用视图的唯一原因,我会进行性能分析。
现在让我们考虑:
CREATE VIEW AllAncestry
AS
WITH recurseAncetry (ancestorID, descendantID)
AS
(
    SELECT parentID, childID
    FROM Parentage
    WHERE parentID IS NOT NULL

    UNION ALL

    SELECT ancestorID, childID
    FROM recurseAncetry
        INNER JOIN Parentage ON parentID = descendantID
)
SELECT DISTINCT (cast(ancestorID as bigint) * 0x100000000 + descendantID) as id, ancestorID, descendantID
FROM recurseAncetry

从概念上讲,这个视图需要执行大量的选择操作;也就是说,进行一次选择,然后基于该选择的结果递归地进行选择,直到获得所有可能的结果。
在实际执行中,这被转换为两个表扫描和一个延迟spool。
基于linq的等效方法会更加繁重;你最好要么调用等效的原始SQL,要么将表加载到内存中,然后在C#中生成完整的图形(但请注意,对于不需要所有内容的查询,这将是浪费)。
总体而言,在这里使用视图可以节约大量时间。
总之,使用视图通常对性能影响微乎其微,并且这种影响可能会有所不同。使用触发器的视图可以带来轻微的性能优势,并使数据完整性更容易得到保证,因为它强制在单个事务中发生。使用CTE的视图可以带来巨大的性能优势。
使用或避免使用视图的非性能原因包括:
1. 使用视图隐藏了与该视图相关联的实体与相关底层表之间的关系,这对于您的模型来说是不好的,因为此时您的模型是不完整的。
2. 如果视图在除了您的应用程序之外的其他应用程序中使用,则您将与这些其他应用程序保持一致,利用已经经过测试的代码,并自动处理视图实现的更改。

4
那篇文章里面有一些非常严谨的微观优化。
个人认为不要把它当成准则,毕竟我用过EF很多次。 这些细节确实会影响性能,但总体来说速度还是很快的。

如果你有一个复杂的视图,并在该视图上执行更多的LINQ操作,那么它可能会导致一些慢速表现,但我不敢确定。

这篇文章甚至没有任何基准测试!

如果性能对您的程序而言是一个严重问题,请缩小查询范围并在此处发布它们,看看SO社区是否能帮助优化查询。如果问我,这比所有微观优化的解决方案要好得多。


但是,访问视图检索数据与从表中检索数据不完全相同,不是吗?查询是否快速取决于数据库的问题,不是吗?我正在使用实体框架访问具有数十亿数据集的视图。我的数据库非常快速。所以我想知道为什么表会更快。 - ˈvɔlə
如果你有一个包含数十亿条记录的视图,无论你做什么,它都不会快。视图实际上仍然是一个查询,尽管涉及多个表,所以是的,与对单个表进行查询相比,对视图进行查询可能会更快,但如果你需要将所有数据组合到一个视图中,那么你只能使用LINQ(可能会有些混乱)或者查询该视图,没有其他选择。 - Trent
是的,我同意。这听起来很合理。 - ˈvɔlə
一个视图不是一个查询,它是查询的一部分描述。无论您是否需要视图执行的所有工作 - 因此差异可以忽略 - 或者冗余将被查询规划器删除 - 因此差异可以忽略。在某些情况下,使用视图对性能造成的影响与您可能拥有多少亿行没有任何关系。 - Jon Hanna

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