问题描述
我不确定如何最好地调整此查询和/或索引以避免生硬的 FORCE ORDER 提示。
此主查询运行良好,当前在 0 秒内返回 0 行:
SELECT S1.ID,S.LOAD_DATE,s.Deleted,S1.HUB_FORM_ID
FROM #TMP S
INNER JOIN HUB_FORM H1 ON
H1.Form_ID = S.HUB_FORM_BK
INNER JOIN HUB_ORG H2 ON
H2.Organisation_ID = S.HUB_ORG_BK
INNER JOIN HUB_PERSON H3 ON
H3.person_id = S.HUB_PERSON_BK
INNER JOIN HUB_EVENT H4 ON
H4.job_id = S.HUB_EVENT_BK
INNER JOIN HUB_WORKFLOW_STEP H5 ON
H5.step_id = S.HUB_WORKFLOW_STEP_BK
INNER JOIN LNK_FORM_ENTITY S1 ON
H1.HUB_FORM_ID = S1.HUB_FORM_ID AND H2.HUB_ORG_ID = S1.HUB_ORG_ID AND H3.HUB_PERSON_ID = S1.HUB_PERSON_ID AND H4.HUB_EVENT_ID = S1.HUB_EVENT_ID AND H5.HUB_WORKFLOW_STEP_ID = S1.HUB_WORKFLOW_STEP_ID
INNER JOIN DK_SAT_LNK_FORM_ENTITY S2 ON
S1.ID = S2.Parent_ID
在 S2.LOAD_DATE_TO 上添加 WHERE 子句使其运行并运行(在一两分钟后终止)。
WHERE S2.LOAD_DATE_TO = '31/12/9999'
我不确定为什么会这样:
- 如果没有过滤器,则不会返回任何行,因此没有任何区别。
- 用于在良好计划中包含此字段的表的索引(没有日期过滤器),已经包含该字段作为第二个关键字段,因此我认为任何额外费用都可以忽略不计
注意 - 它并不总是返回 0 行,但无论行是否返回,它都需要运行(并在合理的时间内完成)。
CREATE NONCLUSTERED INDEX [JM_TEST_190221_2] ON [dbo].[DK_SAT_LNK_FORM_ENTITY]
(
[Parent_ID] ASC,[LOAD_DATE_TO] 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]
GO
实时查询计划显示它运行了 LNK_ 和 DK_ 表以及随后连接的表中的数百万行,而在原始计划中,它显示 LNK_ 表上的实际行数 = 56(56 次执行 - 预期 1 行)和 DK_ 表上的 0 个实际行(56 次执行)。
如果我在 WHERE 子句后添加 OPTION (FORCE ORDER)
,它会在 0 秒内再次运行,并使用与原始好的查询计划不同的查询计划。
很明显,这可以在短期内解决问题,但我对使用这种生硬的工具持谨慎态度,因为随着时间的推移,随着数据的变化,它可能并不总是最佳选择。
编辑 我曾尝试使用 FULL SCAN 更新统计信息,并重建关键索引,但没有任何影响。
原来的好计划(actual plan):没有WHERE子句:https://www.brentozar.com/pastetheplan/?id=HyG3SwTZd
糟糕的计划(来自终止点的实时查询计划):https://www.brentozar.com/pastetheplan/?id=rJpBSPpWO
带有 FORCE ORDER 提示的好计划:https://www.brentozar.com/pastetheplan/?id=SJqxUvT-d
解决方法
显然,您的问题是 HUB_FORM
具有足够的选择性,以至于在一开始就将行数限制为 0。但是优化器没有意识到这一点,因此它颠倒了连接的顺序。
要强制执行订单而不通过 FORCE ORDER
敲打查询的其余部分,我们有两个选择:
- 预先计算
#TMP,HUB_FORM
与临时表或表变量的连接。这通常会导致相当多的额外 IO。 - 更好的选择是说服优化器先计算连接,但不使用显式提示。
这通常最好通过将连接放在带有 SELECT TOP
的子查询中来完成,但您可能需要通过添加一两个进一步的连接来修改它。
SELECT S1.ID,S.LOAD_DATE,s.Deleted,S1.HUB_FORM_ID
FROM (
SELECT TOP (9223372036854775807) S.*
FROM #TMP S
INNER JOIN HUB_FORM H1 ON
H1.Form_ID = S.HUB_FORM_BK
) S
INNER JOIN HUB_ORG H2 ON
H2.Organisation_ID = S.HUB_ORG_BK
INNER JOIN HUB_PERSON H3 ON
H3.person_id = S.HUB_PERSON_BK
INNER JOIN HUB_EVENT H4 ON
H4.job_id = S.HUB_EVENT_BK
INNER JOIN HUB_WORKFLOW_STEP H5 ON
H5.step_id = S.HUB_WORKFLOW_STEP_BK
INNER JOIN LNK_FORM_ENTITY S1 ON
H1.HUB_FORM_ID = S1.HUB_FORM_ID AND H2.HUB_ORG_ID = S1.HUB_ORG_ID AND H3.HUB_PERSON_ID = S1.HUB_PERSON_ID AND H4.HUB_EVENT_ID = S1.HUB_EVENT_ID AND H5.HUB_WORKFLOW_STEP_ID = S1.HUB_WORKFLOW_STEP_ID
INNER JOIN DK_SAT_LNK_FORM_ENTITY S2 ON
S1.ID = S2.Parent_ID
如果这不起作用,您可以通过将 TOP
更改为变量并在末尾添加 OPTIMIZE FOR
提示来说服它:
DECLARE @topRows bigint = 9223372036854775807;
SELECT S1.ID,S1.HUB_FORM_ID
FROM (
SELECT TOP (@topRows) S.*
FROM #TMP S
INNER JOIN HUB_FORM H1 ON
H1.Form_ID = S.HUB_FORM_BK
) S
INNER JOIN HUB_ORG H2 ON
.........
OPTION (OPTIMIZE FOR (@topRows = 1));
这会导致优化器认为它只会从连接中获取 1 行,但如果在运行时是这种情况,实际上允许更多行。
请注意,这些都不会改变查询的基本语义