Ala*_*tts 4 sql-server execution-plan
我正在尝试诊断一个问题,即 2 个非常相似的查询导致执行时间非常不同,即使执行计划非常简单。
大体上(我已经修剪了选择和重命名的表),我们有一个主表 ( [Primary]),我们试图根据相关表中至少 1 个匹配行的存在进行过滤。然后我们返回前 20 行(用于分页)
查询之间唯一的区别是相关表不同(尽管具有相似的结构)。快速查询 ( [PrimaryResult]) 需要 < 1 秒,而慢查询 ( [PrimaryScore]) 需要 20 秒左右。
我检查了执行计划,主要区别在于主表上的键查找。在快速查询中,Actual number of rows read大约为 10k,而对于慢速查询,它超过 360 万。
我观察到的另一件事是快速查询似乎并行执行所有操作(由执行计划中的双箭头表示,但慢速查询没有)。
查询是通过 Entity Framework 6 LINQ 生成的(因此所有别名)。
慢查询
SELECT
[Project5].[Id] AS [Id]
FROM ( SELECT
[Project1].[Id] AS [Id]
FROM ( SELECT
[Extent1].[Id] AS [Id]
FROM [dbo].[Primary] AS [Extent1]
INNER JOIN [dbo].[GuidBatch] AS [Extent2] ON ([Extent1].[DeviceRegistrationId] = [Extent2].[Ref]) AND (@p__linq__0 = [Extent2].[Id])
INNER JOIN [dbo].[Place] AS [Extent3] ON [Extent1].[PlaceId] = [Extent3].[PlaceId]
WHERE [Extent1].[IsValid] = 1
) AS [Project1]
WHERE ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[PrimaryScore] AS [Extent4]
WHERE ([Project1].[Id] = [Extent4].[Id]) AND ([Extent4].[Key] = @p__linq__1) AND ([Extent4].[Score] IN (4,3))
)) OR ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[PrimaryScore] AS [Extent5]
WHERE ([Project1].[Id] = [Extent5].[Id]) AND ([Extent5].[Key] = @p__linq__2)AND ([Extent5].[Score] IN (4,3))
)) OR ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[PrimaryScore] AS [Extent6]
WHERE ([Project1].[Id] = [Extent6].[Id]) AND ([Extent6].[Key] = @p__linq__3) AND ([Extent6].[Score] IN (4,3))
))
) AS [Project5]
ORDER BY row_number() OVER (ORDER BY [Project5].[CaptureDate] DESC)
OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY
Run Code Online (Sandbox Code Playgroud)
快速查询
SELECT
[Project5].[Id] AS [Id]
FROM ( SELECT
[Project1].[Id] AS [Id]
FROM ( SELECT
[Extent1].[Id] AS [Id]
FROM [dbo].[Primary] AS [Extent1]
INNER JOIN [dbo].[GuidBatch] AS [Extent2] ON ([Extent1].[DeviceRegistrationId] = [Extent2].[Ref]) AND (@p__linq__0 = [Extent2].[Id])
INNER JOIN [dbo].[Place] AS [Extent3] ON [Extent1].[PlaceId] = [Extent3].[PlaceId]
WHERE [Extent1].[IsValid] = 1
) AS [Project1]
WHERE ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[PrimaryResult] AS [Extent4]
WHERE ([Project1].[Id] = [Extent4].[Id]) AND ([Extent4].[ActivityId] = @p__linq__1) AND ([Extent4].[SelectedOptionId] IN (cast('8c93216d-53a4-40b3-a905-caaa84c0a09c' as uniqueidentifier), cast('b1f406ab-b009-4851-9200-1a2828bc61e6' as uniqueidentifier), cast('aa8d425d-5f0b-4142-b43b-29fa697f82a6' as uniqueidentifier), cast('8945430c-9ef8-4c53-a228-24b58aa7cf7e' as uniqueidentifier)))
))
OR ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[PrimaryResult] AS [Extent5]
WHERE ([Project1].[Id] = [Extent5].[Id]) AND ([Extent5].[ActivityId] = @p__linq__2) AND ([Extent5].[SelectedOptionId] IN (cast('215e02d9-a96a-43ec-8940-d7561534f352' as uniqueidentifier), cast('cee9415e-0ba9-4b43-ad7b-01c28ed4a9ff' as uniqueidentifier), cast('65655400-865c-4456-82a1-dc8addd705fa' as uniqueidentifier), cast('50d406d0-15f2-45ee-8a9b-3503f8e638b1' as uniqueidentifier)))
)) OR ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[PrimaryResult] AS [Extent6]
WHERE ([Project1].[Id] = [Extent6].[Id]) AND ([Extent6].[ActivityId] = @p__linq__3) AND ([Extent6].[SelectedOptionId] IN (cast('1d1b5f0f-3335-4ad9-96c9-d363bca2f7ae' as uniqueidentifier), cast('d04e21f3-0106-47c5-b79f-b74e6309adb0' as uniqueidentifier), cast('c768ed36-fea2-4e8e-8074-b8a0f5aa6f92' as uniqueidentifier), cast('cc32fa39-fa0f-4545-b01e-d7254b5e6a85' as uniqueidentifier), cast('af768460-5d59-4107-8642-2b22ea2cf73e' as uniqueidentifier)))
))
) AS [Project5]
ORDER BY row_number() OVER (ORDER BY [Project5].[CaptureDate] DESC)
OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY
Run Code Online (Sandbox Code Playgroud)
这是执行计划。我只是混淆了特定的表名
编辑:我上传了一个匿名查询计划。
慢查询 https://www.brentozar.com/pastetheplan/?id=ryXq6AF1H
快速查询:https : //www.brentozar.com/pastetheplan/?id=H1JSpCKkr
我的问题显然是为什么会发生这种情况?我相信我已经在所有表上设置了正确的索引。
另一件要注意的事情是GuidBatch过滤返回最多 1.6mPrimary行,所以我很困惑为什么慢查询读取的数据比这多,但快速查询只读取 10k 行。
我理解匿名化的必要性,但这让分析变得非常困难。也没有一种合理的方法来猜测为什么当您查询两个不同的表时,您会在没有看到表和索引定义的情况下获得不同的性能(除了事实上它们是两个不同的表)。
避免猜测,让我们专注于慢计划:
您可能面临的问题来自(可能参数化的)TOP表达式。UsingTOP引入了一个行目标,它改变了优化器查找数据的策略。
它也可能与编译计划时使用的初始参数集有关,但您已将有关编译和运行时参数的任何有用信息匿名化了。
请注意需要从计划中运行时间最长的部分出来以满足计划中 20 行目标的行数:
即使使用“完美”索引,您也可能遇到这样的问题。您可以尝试修复 Key Lookup 部分,但这需要相当宽的索引。
您需要考虑来自非聚集索引扫描和键查找运算符的谓词和输出列表。不过,这样做可能只会比计划缩短约 5 秒。您还剩下 12 秒的其他内容。
您可以对查询尝试不同的提示,例如OPTION(MERGE JOIN, HASH JOIN);摆脱陷入困境的嵌套循环地狱。不幸的是,除非您愿意将这些查询从 Entity Framework 手中夺走,否则您的优化选择非常有限. 如果暗示的计划之一更好,您可以尝试创建计划指南或使用查询存储来强制执行更好的计划。
一种选择可能是通过仅选择查询所需的列来消除键查找,但如果不查看查询或不了解其要求,就无法判断。