Fru*_*aun 5 sql-server execution-plan view query-performance
我正在努力说服查询计划按照我认为应该的方式行事。在查询索引视图时添加 TOP 子句会导致次优计划,我希望在排序方面得到一些帮助。
环境
设置:
首先,我创建了一个视图来回报每个人的高声誉:
CREATE VIEW vwHighReputation
WITH SCHEMABINDING
AS
SELECT [Id],
[DisplayName],
[Reputation]
FROM [dbo].[Users]
WHERE [Reputation] > 10000
Run Code Online (Sandbox Code Playgroud)
接下来,由于我将按显示名称进行搜索,因此我在视图上创建了几个索引:
CREATE UNIQUE CLUSTERED INDEX IX_Users_Id ON [dbo].[vwHighReputation]([Id])
GO
CREATE NONCLUSTERED INDEX IX_Users_DisplayName ON [dbo].[vwHighReputation]([DisplayName]) INCLUDE (Reputation)
GO
Run Code Online (Sandbox Code Playgroud)
如果我通过视图查询,我可以看到我的非聚集索引正在被使用:
SELECT *
FROM [dbo].[vwHighReputation]
WHERE [DisplayName] LIKE 'J%'
Run Code Online (Sandbox Code Playgroud)
计划:(https://www.brentozar.com/pastetheplan/?id=Sy2EoJaiv)
到现在为止还挺好。我什至可以使用我的视图作为带有 OUTER APPLY 的更复杂查询的一部分,并且我仍然只对索引进行了 63 次读取(这显然是一个人为的示例,但有助于说明我将要解决的问题) ):
SELECT [U].[Id],
[A].[Reputation],
[A].[DisplayName]
FROM [dbo].[Users] AS [U]
OUTER APPLY (
SELECT *
FROM [dbo].[vwHighReputation] AS [v]
WHERE [v].[Id] = [U].[Id]
) AS [A]
WHERE [A].[DisplayName] LIKE 'J%';
Run Code Online (Sandbox Code Playgroud)
计划:https : //www.brentozar.com/pastetheplan/?id=HJaw3y6ov
但是,如果我将 TOP 1 添加到我的 OUTER APPLY:
SELECT [U].[Id],
[A].[Reputation],
[A].[DisplayName]
FROM [dbo].[Users] AS [U]
OUTER APPLY (
SELECT TOP 1 *
FROM [dbo].[vwHighReputation] AS [v]
WHERE [v].[Id] = [U].[Id]
) AS [A]
WHERE [A].[DisplayName] LIKE 'J%';
Run Code Online (Sandbox Code Playgroud)
然后情况变得很糟糕......非常非常糟糕......
计划:https : //www.brentozar.com/pastetheplan/?id=HyOS6yaiw
我针对该视图的逻辑读取计数现在接近 500 万。我可以从计划中看到 SQL Server 现在选择以用户 ID 作为谓词对聚集索引执行搜索,但这样做了大约 250 万次。它还扫描整个用户表。它不再寻找视图的索引。
显然优化器决定这是最有效的方法,但我不明白为什么!我认为这可能与底层表的排序方式有关,但我不确定。
顺便说一句,将其重写为简单的 SUB QUERY 而不是 CROSS APPLY 会产生相同的结果。
任何帮助或建议都会很棒!
您正在使用OUTER APPLY
, 但带有拒绝NULL
值的 where 子句。
它被转换为没有以下内容的内部联接TOP (1)
:
SELECT
U.Id,
A.Reputation,
A.DisplayName
FROM dbo.Users AS U
OUTER APPLY
(
SELECT
v.*
FROM dbo.vwHighReputation AS v
WHERE v.Id = U.Id
) AS A
WHERE A.DisplayName LIKE 'J%'
ORDER BY U.Id;
Run Code Online (Sandbox Code Playgroud)
我已经稍微格式化了您的代码,并添加了一个ORDER BY
来验证跨查询的结果。没有冒犯的意思。
当您使用 时TOP (1)
,连接是LEFT OUTER
多种多样的:
SELECT
U.Id,
A.Reputation,
A.DisplayName
FROM dbo.Users AS U
OUTER APPLY
(
SELECT TOP (1)
v.*
FROM dbo.vwHighReputation AS v
WHERE v.Id = U.Id
) AS A
WHERE A.DisplayName LIKE 'J%'
ORDER BY U.Id;
Run Code Online (Sandbox Code Playgroud)
该TOP (1)
内部OUTER APPLY
显然使得优化器无法相同的变换应用到内部连接,即使有多余的谓词:
SELECT
U.Id,
A.Reputation,
A.DisplayName
FROM dbo.Users AS U
OUTER APPLY
(
SELECT TOP (1)
v.*
FROM dbo.vwHighReputation AS v
WHERE v.Id = U.Id
AND v.DisplayName LIKE 'J%'
) AS A
WHERE A.DisplayName LIKE 'J%'
ORDER BY U.Id;
Run Code Online (Sandbox Code Playgroud)
请注意残差谓词以评估Id
和DisplayName
列是否为NULL
。
这也不仅仅是一个TOP (1)
问题——您可以替换任何不超过 big int max (9223372036854775807) 的值并查看相同的计划。
如果您完全跳过视图,也会发生这种情况。
SELECT
U.Id,
A.Reputation,
A.DisplayName
FROM dbo.Users AS U
OUTER APPLY
(
SELECT TOP (1)
v.Id,
v.DisplayName,
v.Reputation
FROM dbo.Users AS v
WHERE v.Reputation > 10000
AND v.Id = U.Id
) AS A
WHERE A.DisplayName LIKE 'J%'
ORDER BY U.Id
OPTION(EXPAND VIEWS);
Run Code Online (Sandbox Code Playgroud)
获得与TOP (1)
没有各种优化器副作用相同效果的一种方法TOP
是使用ROW_NUMBER
SELECT
U.Id,
A.Reputation,
A.DisplayName
FROM dbo.Users AS U
OUTER APPLY
(
SELECT
v.*
FROM
(
SELECT
v.*,
ROW_NUMBER() OVER
(
PARTITION BY
v.Id
ORDER BY
v.Id
) AS n
FROM dbo.vwHighReputation AS v
) AS v
WHERE v.Id = U.Id
AND v.n = 1
) AS A
WHERE A.DisplayName LIKE 'J%'
ORDER BY U.Id;
Run Code Online (Sandbox Code Playgroud)
这将为您提供原始计划:
归档时间: |
|
查看次数: |
209 次 |
最近记录: |