存储过程查询有时会在其中一个表的统计信息更新后得到一个糟糕的计划,但之后可以立即重新编译为好的计划。相同的编译参数。
问题似乎来自在 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
内部视图
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
计划
附加信息
当时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>)
返回:
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
很明显,这种搜索很糟糕,但问题是为什么它一开始就没有做好搜索。
查询没有返回 1m 行,估计是错误的。输出可能会有细微的变化,但行数总是很低(最多可能数百)。
即使是返回相对多行的那些也会生成由 theId
而不是由 the搜索的计划status
(如您所见,这不是选择性的)。无论编译什么值,我似乎都无法重现状态寻求计划。我什至尝试waitfor delay
在临时表的创建和第二个查询之间添加一个,并在第二个会话中更新统计信息/重新编译,也没有任何效果。