我怎样才能加快这个索引视图?

Posted

技术标签:

【中文标题】我怎样才能加快这个索引视图?【英文标题】:How can i speed up this Indexed View? 【发布时间】:2010-11-03 13:07:02 【问题描述】:

我有一个简单的索引视图。当我查询它时,它很慢。首先,我向您展示模式和索引。然后是简单的查询。最后是查询计划屏幕。

更新:本文底部的解决方案证明。

架构

这就是它的样子:-

CREATE view [dbo].[PostsCleanSubjectView] with SCHEMABINDING AS
    SELECT PostId, PostTypeId, 
        [dbo].[ToUriCleanText]([Subject]) AS CleanedSubject
    FROM [dbo].[Posts]

我的 udf ToUriCleanText 只是用空字符替换各种字符。例如。用 '' 替换所有 '#' 字符。

然后我添加了两个索引:-

索引

主键索引(即聚集索引)

CREATE UNIQUE CLUSTERED INDEX [PK_PostCleanSubjectView] ON 
    [dbo].[PostsCleanSubjectView] 
(
    [PostId] ASC
)
WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
      SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF,
      ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

还有一个非聚集索引

CREATE NONCLUSTERED INDEX [IX_PostCleanSubjectView_PostTypeId_Subject] ON 
    [dbo].[PostsCleanSubjectView] 
(
    [CleanedSubject] ASC,
    [PostTypeId] ASC
)
WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
      SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF,
      ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

现在,它有大约 25K 行。没什么大不了的。

当我执行以下查询时,它们都需要大约 4 秒。怎么回事?这应该是..基本上是即时的!

查询 1

SELECT a.PostId
FROM PostsCleanSubjectView a 
WHERE a.CleanedSubject = 'Just-out-of-town'

查询 2(添加了另一个 where 子句项)

SELECT a.PostId
FROM PostsCleanSubjectView a 
WHERE a.CleanedSubject = 'Just-out-of-town' AND a.PostTypeId = 1

我做错了什么? UDF 搞砸了?我认为,因为我已经索引了这个观点,所以它会成为现实。因此,它不必计算该字符串列。

这是查询计划的屏幕截图,如果有帮助的话:-

另外,请注意它使用的索引?为什么要使用那个索引?

那个索引是……

CREATE NONCLUSTERED INDEX [IX_Posts_PostTypeId_Subject] ON [dbo].[Posts] 
(
    [PostTypeId] ASC,
    [Subject] ASC
)
WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
      SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, 
      ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

是的,有什么想法吗?

更新 1:为 udf 添加了架构。

CREATE FUNCTION [dbo].[ToUriCleanText]
(
    @Subject NVARCHAR(300)
)
RETURNS NVARCHAR(350) WITH SCHEMABINDING
AS 
BEGIN
   <snip>
   // Nothing insteresting in here. 
   //Just lots of SET @foo = REPLACE(@foo, '$', ''), etc.
END

更新 2:解决方案

是的,这是因为我没有在视图上使用索引,并且必须手动确保我没有展开视图。服务器为Sql Server 2008 标准版。完整答案如下。 这是证据,WITH (NOEXPAND)

谢谢大家帮我解决这个问题:)

【问题讨论】:

ToUriCleanText 的确切返回类型是什么。是 varchar(max) 还是 nvarchar(max) ? 使用 UDF 架构更新原始帖子。返回 NVARCHAR(350)。 为什么不直接使用 REPLACE(REPLACE(REPLACE ....)))?它可能会更快,当然不会更慢。 真的那么快吗?目前我有.. er.. 15 左右 SET 行。 【参考方案1】:

什么版本的 SQL Server?我相信只有 Enterprise 和 Developer Edition 会自动使用索引视图,而其他版本则使用查询提示来支持它。

SELECT a.PostId
FROM PostsCleanSubjectView a WITH (NOEXPAND)
WHERE a.CleanedSubject = 'Just-out-of-town' AND a.PostTypeId = 1

来自Query Hints (Transact SQL) on MSDN:

只有在查询的 SELECT 部分直接引用视图并指定 WITH (NOEXPAND) 或 WITH (NOEXPAND, INDEX( index_value [ ,...n ] ) ) 时,索引视图才不会展开。

【讨论】:

我使用的是 Sql Server 标准版。好的,让我们试试这个... ##### HOLY MOLLY。 #### 立即的!!!! (NOEXPAND) 到底是做什么的?这是我所期望的! :) @Pure.Krome,视图可以(并且通常是)扩展,这意味着 SQL Server 将查询原始视图(在您的情况下为基表)以获取数据。这有点类似于 WITH 语句。但是,这也意味着放置在视图上的任何索引都不会被使用。查询提示 NOEXPAND 告诉您它不应该将查询扩展到任何基础视图,这将在其上使用索引。 干杯伙伴 - 有意义。非常棒。非常感谢。每个人都很好地尝试了这一点,而您却发现了问题,我对此感到谦卑。干杯! WITH (NOEXPAND) :在几次击键中从疯狂到奇妙。感谢您的洞察力。【参考方案2】:

我在您的执行计划中的查询代码中看到了一个 @ 符号。涉及到一个字符串变量。

如果字符串变量的类型与索引中字符串列的类型不匹配,Sql Server 会出现 NASTY 行为。 Sql Server 将...将整个列转换为该类型,执行快速查找,然后丢弃转换后的索引,以便在下一次查询时再次执行整个操作。


Simon 想通了 - 但这里有更多有用的细节:http://msdn.microsoft.com/en-us/library/ms187373.aspx

如果查询包含对索引视图和基表中都存在的列的引用,并且查询优化器确定使用索引视图提供了执行查询的最佳方法,则查询优化器将使用视图上的索引.此功能称为索引视图匹配,仅在 SQL Server Enterprise 和 Developer 版本中受支持。

但是,为了让优化器考虑匹配索引视图或使用通过 NOEXPAND 提示引用的索引视图,必须将以下 SET 选项设置为 ON:

所以,这里发生的是 索引视图匹配 不起作用。确保您使用的是 Sql Server 的 Enterprise 或 Developer 版本(很可能)。然后根据文章检查您的 SET 选项。

【讨论】:

是的,经常看到这个。许多人不理解数据类型优先级的影响 有趣。我不知道。我已确保数据类型属于同一类型。 伟大的编辑大卫。这非常非常有帮助,是的 -> 这就是问题所在 :) 干杯!【参考方案3】:

我最近构建了一个包含数亿条呼叫详细记录的大型数据库,并且我在查询和视图中使用了一些函数,这些函数我变成了持久计算列。这效果要好得多,因为我可以在计算列上建立索引。

虽然我没有使用 SQL Enterprise,所以我没有机会使用索引视图。索引视图是否应该能够索引 UDF 的确定性结果?

【讨论】:

索引视图在所有版本中都可用,但是,我相信只有 Enterprise/Developer 会自动使用索引。如果您想在其他版本中使用索引视图,则需要使用查询提示 (noexpand)。【参考方案4】:

我怀疑它必须为每一行调用该函数,然后才能在 where 子句中进行比较。我会公开主题,直接运行查询检查并查看时间如何计算。每当我使用函数修改值然后在 where 子句中使用它时,我通常会看到很多缓慢...

【讨论】:

【参考方案5】:

您希望通过使用索引视图获得什么好处?是否无法正确索引表本身?如果没有充分的理由,您会增加复杂性并要求优化器以较低的灵活性来处理更多的数据库对象。

您是否使用标准索引评估过相同的查询逻辑?

混入 UDF 逻辑会使事情更加混乱。

【讨论】:

我同意增加的复杂性,但是这个视图的价值是用在逻辑上的。因此,我不想在原始 Posts 表中添加这个(未使用的)数据。我使用这个视图逻辑来确定一个不同的最终值,该值进入 Posts 表。通过使其成为索引视图,我希望删除临时表或变量表的使用,并且该视图已实现。 我们能看到更多你的观点吗?我的经验是,通常有不那么激进、更轻量级的策略来分解临时表和变量表。对于 25K 行,这太糟糕了。【参考方案6】:

如果您只想持久化 UDF 的返回值,请考虑持久化计算列而不是索引视图。

【讨论】:

【参考方案7】:

对于某些数据访问层,例如 EF Core,很难添加 NOEXPAND - 因此您可以创建一个额外的视图(非架构绑定)并将其添加到那里。你可以随心所欲地称呼它,但我喜欢明确地将 NOEXPAND 放在名称中以作为提醒。

CREATE VIEW [dbo].[DailySummary_NOEXPAND]
AS
SELECT Col1, Col2, Col3 FROM [dbo].[DailySummary] WITH (NOEXPAND)
END

还在 Azure SQL(标准层)上进行了测试

重要提示:您可以使用SELECT *,但如果底层视图发生更改,则列索引可能会不同步,您实际上可能会返回错误的数据。 specify columns by name 更安全。

【讨论】:

以上是关于我怎样才能加快这个索引视图?的主要内容,如果未能解决你的问题,请参考以下文章

我怎样才能加快这个迭代?

双连接查询需要 540 秒才能运行 - 我怎样才能加快速度?

我怎样才能改变这种看法?

我怎样才能加快这个 Anagram 算法

SQLite3-我怎样才能加快这个 SELECT 查询?

我的子查询将执行时间增加了 20 秒。我怎样才能加快速度?