Cho*_*man 9 performance sql-server optimization execution-plan query-performance
我有两个非常相似的查询
第一个查询:
SELECT count(*)
FROM Audits a
JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
WHERE
ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
and a.TargetTypeId IN
(1,2,3,4,5,6,7,8,9,
11,12,13,14,15,16,17,18,19,
21,22,23,24,25,26,27,28,29,30,
31,32,33,34,35,36,37,38,39,
41,42,43,44,45,46,47,48,49,
51,52,53,54,55,56,57,58,59,
61,62,63,64,65,66,67,68,69,
71,72,73,74,75,76,77,78,79)
Run Code Online (Sandbox Code Playgroud)
结果:267479
计划:https : //www.brentozar.com/pastetheplan/?id=BJWTtILyS
第二个查询:
SELECT count(*)
FROM Audits a
JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
WHERE
ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
and a.TargetTypeId IN
(1,2,3,4,5,6,7,8,9,
11,12,13,14,15,16,17,18,19,
21,22,23,24,25,26,27,28,29,
31,32,33,34,35,36,37,38,39,
41,42,43,44,45,46,47,48,49,
51,52,53,54,55,56,57,58,59,
61,62,63,64,65,66,67,68,69,
71,72,73,74,75,76,77,78,79)
Run Code Online (Sandbox Code Playgroud)
结果:25650
计划:https : //www.brentozar.com/pastetheplan/?id=S1v79U8kS
第一个查询大约需要一秒钟才能完成,而第二个查询大约需要 20 秒。这对我来说完全违反直觉,因为第一个查询的计数比第二个查询高得多。这是在 SQL Server 2012 上
为什么差别这么大?如何将第二个查询加速到与第一个查询一样快?
这是两个表的创建表脚本:
CREATE TABLE [dbo].[AuditRelatedIds](
[AuditId] [bigint] NOT NULL,
[RelatedId] [uniqueidentifier] NOT NULL,
[AuditTargetTypeId] [smallint] NOT NULL,
CONSTRAINT [PK_AuditRelatedIds] PRIMARY KEY CLUSTERED
(
[AuditId] ASC,
[RelatedId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_AuditRelatedIdsRelatedId_INCLUDES] ON [dbo].[AuditRelatedIds]
(
[RelatedId] ASC
)
INCLUDE ( [AuditId]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
ALTER TABLE [dbo].[AuditRelatedIds] WITH CHECK ADD CONSTRAINT [FK_AuditRelatedIds_AuditId_Audits_Id] FOREIGN KEY([AuditId])
REFERENCES [dbo].[Audits] ([Id])
ALTER TABLE [dbo].[AuditRelatedIds] CHECK CONSTRAINT [FK_AuditRelatedIds_AuditId_Audits_Id]
ALTER TABLE [dbo].[AuditRelatedIds] WITH CHECK ADD CONSTRAINT [FK_AuditRelatedIds_AuditTargetTypeId_AuditTargetTypes_Id] FOREIGN KEY([AuditTargetTypeId])
REFERENCES [dbo].[AuditTargetTypes] ([Id])
ALTER TABLE [dbo].[AuditRelatedIds] CHECK CONSTRAINT [FK_AuditRelatedIds_AuditTargetTypeId_AuditTargetTypes_Id]
Run Code Online (Sandbox Code Playgroud)
CREATE TABLE [dbo].[Audits](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[TargetTypeId] [smallint] NOT NULL,
[TargetId] [nvarchar](40) NOT NULL,
[TargetName] [nvarchar](max) NOT NULL,
[Action] [tinyint] NOT NULL,
[ActionOverride] [tinyint] NULL,
[Date] [datetime] NOT NULL,
[UserDisplayName] [nvarchar](max) NOT NULL,
[DescriptionData] [nvarchar](max) NULL,
[IsNotification] [bit] NOT NULL,
CONSTRAINT [PK_Audits] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
SET ANSI_PADDING ON
CREATE NONCLUSTERED INDEX [IX_AuditsTargetId] ON [dbo].[Audits]
(
[TargetId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
SET ANSI_PADDING ON
CREATE NONCLUSTERED INDEX [IX_AuditsTargetTypeIdAction_INCLUDES] ON [dbo].[Audits]
(
[TargetTypeId] ASC,
[Action] ASC
)
INCLUDE ( [TargetId],
[UserDisplayName]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY]
ALTER TABLE [dbo].[Audits] WITH CHECK ADD CONSTRAINT [FK_Audits_TargetTypeId_AuditTargetTypes_Id] FOREIGN KEY([TargetTypeId])
REFERENCES [dbo].[AuditTargetTypes] ([Id])
ALTER TABLE [dbo].[Audits] CHECK CONSTRAINT [FK_Audits_TargetTypeId_AuditTargetTypes_Id]
Run Code Online (Sandbox Code Playgroud)
Tl;博士在底部
选择一种计划而不是另一种计划的主要原因是Estimated total subtree成本。
与性能较好的计划相比,糟糕计划的成本更低。
坏计划的总估计子树成本:
更好执行计划的总估计子树成本
运营商预估成本
某些运营商可以承担大部分成本,这可能是优化器选择不同路径/计划的原因。
在我们更好的执行计划中,大部分Subtreecost是根据index seek&nested loops operator执行连接计算的:
而对于我们糟糕的查询计划,Clustered index seek算子成本更低
这应该解释为什么可以选择另一个计划。
(并通过添加参数来30增加已超出871.510000估计成本的坏计划的成本)。 估计猜测™
更好的执行计划
糟糕的计划
这会把我们带到哪里?
此信息使我们能够在我们的示例中强制执行错误的查询计划 (有关用于复制问题的数据,请参阅 DML 以几乎复制 OP 的问题)
通过添加INNER LOOP JOIN连接提示
SELECT count(*)
FROM Audits a
INNER LOOP JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
WHERE
ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
and a.TargetTypeId IN
(1,2,3,4,5,6,7,8,9,
11,12,13,14,15,16,17,18,19,
21,22,23,24,25,26,27,28,29,
31,32,33,34,35,36,37,38,39,
41,42,43,44,45,46,47,48,49,
51,52,53,54,55,56,57,58,59,
61,62,63,64,65,66,67,68,69,
71,72,73,74,75,76,77,78,79)
Run Code Online (Sandbox Code Playgroud)
它更接近,但有一些连接顺序差异:
重写
我的第一次重写尝试可能是将所有这些数字存储在一个临时表中:
CREATE TABLE #Numbers(Numbering INT)
INSERT INTO #Numbers(Numbering)
VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(11),(12),(13),(14),(15),(16),(17),(18),(19),
(21),(22),(23),(24),(25),(26),(27),(28),(29),(30),(31),(32),(33),(34),(35),
(36),(37),(38),(39),(41),(42),(43),(44),(45),(46),(47),(48),(49),(51),(52),
(53),(54),(55),(56),(57),(58),(59),(61),(62),(63),(64),(65),(66),(67),(68),
(69),(71),(72),(73),(74),(75),(76),(77),(78),(79);
Run Code Online (Sandbox Code Playgroud)
然后添加一个JOIN而不是大IN()
SELECT count(*)
FROM Audits a
INNER LOOP JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
INNER JOIN #Numbers
ON Numbering = a.TargetTypeId
WHERE
ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1';
Run Code Online (Sandbox Code Playgroud)
我们的查询计划不同但尚未修复:
AuditRelatedIds桌上有巨大的估计运营商成本
这是我注意到的地方
我不能直接重新创建您的计划的原因是优化的位图过滤。
我可以通过使用 traceflags 7497&禁用优化的位图过滤器来重新创建您的计划7498
SELECT count(*)
FROM Audits a
INNER JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
INNER JOIN #Numbers
ON Numbering = a.TargetTypeId
WHERE
ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
OPTION (QUERYTRACEON 7497, QUERYTRACEON 7498);
Run Code Online (Sandbox Code Playgroud)
有关优化位图过滤器的更多信息,请访问此处。
这意味着,如果没有位图过滤器,优化器认为最好先连接到#number表,然后再连接到AuditRelatedIds表。
在强制执行命令时, OPTION (QUERYTRACEON 7497, QUERYTRACEON 7498, FORCE ORDER);我们可以看到原因:
不好
删除与 maxdop 1 并行的能力
添加MAXDOP 1查询时执行速度更快,单线程。
并添加此索引
CREATE NONCLUSTERED INDEX [IX_AuditRelatedIdsRelatedId_AuditId] ON [dbo].[AuditRelatedIds]
(
[RelatedId] ASC,
[AuditId] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];
Run Code Online (Sandbox Code Playgroud)
当我们删除强制顺序查询提示或不使用#Numbers 表并使用IN()代替时,情况也是如此。
我的建议是考虑添加MAXDOP(1)并查看是否对您的查询有帮助,并在需要时进行重写。
当然,您还应该记住,在我看来,由于优化了位图过滤并且实际上使用了多线程以达到良好的效果,它的性能甚至更好:
TL; 博士
估计成本将定义所选择的计划,我能够复制行为并看到optimized bitmap filters+parallellism运算符添加到我的最后以高性能和快速的方式执行查询。
您可以考虑添加MAXDOP(1)到您的查询中,希望每次都能获得相同的受控结果,merge join并且没有 'bad' parallellism。
升级到更新的版本并使用更高的基数估计器版本 CardinalityEstimationModelVersion="70"也可能有所帮助。
用于多值过滤的数字临时表也有帮助。
我花在这上面的时间比我愿意承认的要多
set NOCOUNT ON;
DECLARE @I INT = 0
WHILE @I < 56
BEGIN
INSERT INTO [dbo].[Audits] WITH(TABLOCK)
([TargetTypeId],
[TargetId],
[TargetName],
[Action],
[ActionOverride] ,
[Date] ,
[UserDisplayName],
[DescriptionData],
[IsNotification])
SELECT top(500000) CASE WHEN ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 10000 = 30 then 29 ELSE ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 10000 END as rownum2 -- TILL 50 and no 30
,'bla','bla2',1,1,getdate(),'bla3','Bla4',1
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2;
SET @I +=1;
END
-- 'Bad Query matches'
INSERT INTO [dbo].[AuditRelatedIds] WITH(TABLOCK)
([AuditId] ,
[RelatedId] ,
[AuditTargetTypeId])
SELECT
TOP(25650)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum1,
('1DD87CF1-286B-409A-8C60-3FFEC394FDB1') ,
CASE WHEN ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 510 = 30 then 29 ELSE ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 510 END as rownum2 -- TILL 50 and no 30
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2
-- Extra matches with 30
SELECT MAX([Id]) FROM [dbo].[Audits];
--28000001 Upper value
INSERT INTO [dbo].[Audits] WITH(TABLOCK)
([TargetTypeId],
[TargetId],
[TargetName],
[Action],
[ActionOverride] ,
[Date] ,
[UserDisplayName],
[DescriptionData],
[IsNotification])
SELECT top(241829) 30 as rownum2 -- TILL 50 and no 30
,'bla','bla2',1,1,getdate(),'bla3','Bla4',1
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2;
;WITH CTE AS
(SELECT
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum1,
('1DD87CF1-286B-409A-8C60-3FFEC394FDB1') as gu ,
30 as rownum2 -- TILL 50 and no 30
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2
CROSS APPLY master.dbo.spt_values spt3
)
--267479 - 25650 = 241829
INSERT INTO [dbo].[AuditRelatedIds] WITH(TABLOCK)
([AuditId] ,
[RelatedId] ,
[AuditTargetTypeId])
SELECT TOP(241829) rownum1,gu,rownum2 FROM CTE
WHERE rownum1 > 28000001
ORDER BY rownum1 ASC;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
236 次 |
| 最近记录: |