Ale*_*man 8 sql-server optimization statistics execution-plan temporary-tables
存储过程查询有时会在其中一个表上的统计信息更新后得到一个糟糕的计划,但之后可以立即重新编译为好的计划。相同的编译参数。
问题似乎来自在 SP 中创建然后加入的一个小临时表。错误的计划在临时表上有一个警告,即连接列没有统计信息。是什么赋予了?
SQL Server 2016 SP1 CU4,具有 2014 兼容级别
糟糕的计划:
好计划:
USE AppDB
GO
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE PROCEDURE [MySchema].[MySP]
@MyId VARCHAR(50),
@Months INT
AS
BEGIN
SET NOCOUNT ON
SELECT *
INTO #MyTemp
FROM AppDB.MySchema.View_Feeder vf WITH (NOLOCK)
WHERE vf.MyId = @MyId AND vf.Status IS NOT NULL
SELECT wd.Col1
, vp.Col2
, vp.Col3
FROM AppDB.MySchema.View_VP vp WITH (FORCESEEK)
INNER JOIN #MyTemp wd ON wd.Col1 = vp.Col1
WHERE vp.Col3 > DATEADD(MONTH, @Months * -1, GETDATE())
END
Run Code Online (Sandbox Code Playgroud)
USE AppDB
GO
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE VIEW [MySchema].[View_VP]
AS
SELECT pp.Col1,
pd.Col2 AS Col2,
MAX(pp.Col4) AS Col3
FROM P_DB..LargeTable pp WITH (NOLOCK)
INNER JOIN P_DB..SmallTable pd WITH (NOLOCK) ON pp.P_Id = pd.P_Id
WHERE pp.[Status] IN (3, 4)
GROUP BY pp.Col1, pd.Col2
Run Code Online (Sandbox Code Playgroud)
该FORCESEEK
提示当时加入到尝试处理这个非常相同的问题和稳定计划。无论如何,无论有没有它,我真的很想了解这里发生了什么。
我无法随意重现该问题,因此很难说用SELECT INTO
显式表替换是否会有所作为。但是,我相信它的行为方式应该相同。
SELECT
database_id,
is_auto_create_stats_on,
is_auto_update_stats_on,
is_auto_update_stats_async_on
FROM sys.databases
WHERE
database_id IN (2, <relevant user databases>)
Run Code Online (Sandbox Code Playgroud)
返回:
database_id is_auto_create_stats_on is_auto_update_stats_on is_auto_update_stats_async_on
------------- ------------------------- ------------------------- -------------------------------
2 1 1 0
7 1 1 1
37 1 1 1
Run Code Online (Sandbox Code Playgroud)
很明显,这种搜索很糟糕,但问题是为什么它首先不能进行良好的搜索。
查询没有返回 1m 行,估计是错误的。输出可能会有轻微的变化,但行数总是很低(最多可能是数百)。
即使返回相对多行的那些也会生成由 theId
和从不由 the寻找的计划status
(正如您所见,这不是选择性的)。无论编译什么值,我似乎都无法重现状态寻求计划。我什至尝试waitfor delay
在创建临时表和第二个查询之间添加一个,并在第二个会话中更新统计信息/重新编译,但也没有任何效果。
Pau*_*ite 12
错误的计划在临时表上有一个警告,即连接列没有统计信息。是什么赋予了?
这可能有一个更深奥的原因,但更可能是简单的统计创建失败。例如,当任务无法获得所需的内存资源时,或者统计创建受到限制(并发编译过多)时,可能会发生这种情况。请参阅Microsoft SQL Server 2008 中查询优化器使用的 Microsoft 白皮书统计信息。您可以通过同时查看自动统计分析器或扩展事件和其他事件来进一步调试。
也就是说,需要更多的信息和调查才能将计划选择的责任归咎于缺少临时表统计信息。即使没有详细的统计信息,优化器仍然可以看到临时表的总基数,这在这里似乎是一个重要因素。
...但可以立即重新编译为好的计划。相同的编译参数。
该@Months
参数可能是相同的,但在临时表中的行数(从未知视图View_Feeder
)是不同的,所提供的计划不显示的值@MyId
。
从可用信息出发:“好”计划(仅估计值,未提供性能数据)基于包含4 行的临时表。“坏计划”基于一个有114 行的临时表。当然,缺乏密度和直方图信息可能没有帮助,但很容易看出优化器如何为 4 行和 114 行选择不同的计划,尽管密度和分布未知。
如果对不依赖于临时表的计划运算符的估计非常偏离,这是一个强烈的信号,表明当前的主表统计信息不能代表基础数据。由于问题中缺乏信息,因此无法对其进行评估。
尽管如此,还是可以看到优化器被要求在次优备选方案之间进行选择。所提出的两个计划都没有代表“显然是好的”选择,因为两者都涉及查找(缺乏“覆盖”索引)和后期过滤(见下文)。尤其是查找具有与它们相关的高成本,这敏感地取决于基数估计。
使用视图限制优化器和提示选择:
GROUP BY
防止谓词vp.Col3 > DATEADD(MONTH, @Months * -1, GETDATE())
被下推的 a ,即使转换在这种非常特殊的情况下是有效的。
FORCESEEK
只是要求优化器找到任何索引搜索计划(不一定使用您喜欢的索引)。删除视图同样会删除此限制。允许谓词下推也应该为大表打开索引机会。例如:
CREATE INDEX give_me_a_good_name
ON dbo.LargeTable (Col1, [Status], Col4)
INCLUDE (P_Id);
Run Code Online (Sandbox Code Playgroud)
...为重写的查询提供了良好的访问路径:
DECLARE @Date datetime = DATEADD(MONTH, @Months * -1, GETDATE());
SELECT
MT.Col1,
ST.Col2,
MAX(LT.Col4)
FROM #MyTemp AS MT
JOIN dbo.LargeTable AS LT
ON LT.Col1 = MT.Col1
JOIN dbo.SmallTable AS ST
ON ST.P_id = LT.P_Id
WHERE
LT.[Status] IN (3, 4)
AND LT.Col4 > @Date
GROUP BY
MT.Col1,
ST.Col2
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)
另一个考虑因素是临时表和统计缓存的影响,如我的文章存储过程中的临时表和临时表缓存解释中所述。如果一个好的计划依赖于临时对象的当前内容,在UPDATE STATISTICS #MyTemp;
主查询之前显式,并添加OPTION (RECOMPILE)
到主查询可能是一个很好的解决方案。
或者,如果一个特定的计划形状对于此查询始终是最佳的,则您有许多可用选项,包括各种提示、计划指南和查询存储计划强制。您可能会发现使用表变量而不是临时表是更好的选择,因为它有利于低基数的情况,并且不提供(或依赖)统计信息。
总之,在担心临时表上偶尔丢失统计数据的原因(影响)之前,应该进行一些一般性改进:
RECOMPILE
计划选择是否对参数值非常敏感UPDATE STATISTICS
以及RECOMPILE
缓存的统计信息是否有问题SELECT INTO
是否为优化器提供了有用的信息NOLOCK
撒提示来提高性能以下内容是根据提供的编辑执行计划中可用的有限信息构建的:
DROP VIEW IF EXISTS dbo.View_VP;
DROP TABLE IF EXISTS dbo.SmallTable, dbo.LargeTable, #MyTemp;
GO
CREATE TABLE LargeTable (P_Id integer NOT NULL, Status integer NOT NULL, Col1 integer NOT NULL, Col4 datetime NOT NULL);
CREATE TABLE SmallTable (P_id integer NOT NULL, Col2 integer NOT NULL)
CREATE TABLE #MyTemp (Col1 integer NOT NULL);
GO
CREATE VIEW dbo.View_VP
AS
SELECT
pp.Col1,
pd.Col2 AS Col2,
MAX(pp.Col4) AS Col3
FROM LargeTable pp
JOIN SmallTable pd
ON pd.P_id = pp.P_Id
WHERE
pp.[Status] IN (3, 4)
GROUP BY
pp.Col1, pd.Col2;
GO
CREATE UNIQUE CLUSTERED INDEX PK_SmallTable ON dbo.SmallTable (P_id)
CREATE CLUSTERED INDEX ix_P_id ON dbo.LargeTable (P_Id)
CREATE INDEX ix_Col1 ON dbo.LargeTable (Col1)
CREATE INDEX ix_Status ON dbo.LargeTable ([Status])
GO
UPDATE STATISTICS dbo.LargeTable WITH ROWCOUNT = 32268200, PAGECOUNT = 322682;
UPDATE STATISTICS dbo.SmallTable WITH ROWCOUNT = 6349, PAGECOUNT = 63;
UPDATE STATISTICS #MyTemp WITH ROWCOUNT = 4;
Run Code Online (Sandbox Code Playgroud)
查询是:
DECLARE @Months integer = 6;
SELECT wd.Col1
, vp.Col2
, vp.Col3
FROM dbo.View_VP vp WITH (FORCESEEK)
INNER JOIN #MyTemp wd ON wd.Col1 = vp.Col1
WHERE vp.Col3 > DATEADD(MONTH, @Months * -1, GETDATE())
Run Code Online (Sandbox Code Playgroud)
没有关于基表的真实统计数据,这有利于接近“坏计划”示例的计划(使用ix_Status
):
这表明关于 选择性的信息Col1
是优化器选择的重要因素。
归档时间: |
|
查看次数: |
3266 次 |
最近记录: |