13

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

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

架构

这就是它的样子:-

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

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

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

索引

主键索引(即聚集索引)

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) 替代文字

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

4

7 回答 7

20

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

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

MSDN 上的查询提示(Transact SQL)

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

于 2009-06-17T04:14:10.160 回答
4

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

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


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

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

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

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

于 2009-06-17T03:30:32.450 回答
0

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

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

于 2009-06-17T03:40:43.317 回答
0

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

于 2009-06-17T03:48:33.350 回答
0

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

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

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

于 2009-06-17T03:50:04.020 回答
0

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

于 2009-06-17T04:24:12.360 回答
0

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

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

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

重要提示:您可以使用SELECT *,但如果底层视图发生更改,则列索引可能会不同步,您实际上可能会得到错误的数据。按名称指定列要安全得多。

于 2020-05-17T00:54:34.217 回答