SEa*_*986 6 sql-server hints execution-plan sql-server-2016 query-store
我有一个查询,我在查询存储中强制执行一个计划(该计划是为此查询编译的 SQL Server)如果我在强制执行该计划后立即运行该查询,NO_PLAN
尽管数据库没有发生任何更改,但我会得到last_force_failure_reason_desc。我可以成功地对同一查询强制执行不同的计划
这个问题可以用下图来说明:
创建我们的测试数据库
USE [master]
CREATE DATABASE NO_PLAN
ALTER DATABASE [NO_PLAN] SET QUERY_STORE = ON
ALTER DATABASE [NO_PLAN] SET QUERY_STORE (OPERATION_MODE = READ_WRITE, QUERY_CAPTURE_MODE = ALL)
GO
USE NO_PLAN
GO
IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'MyTableA') DROP TABLE MyTableA
IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'MyTableB') DROP TABLE MyTableB
/* create our tables */
CREATE TABLE [dbo].[MyTableA](
[Column1] VARCHAR(50) NULL ,
[Column2] VARCHAR(255) NULL ,
[Column3] INT NULL ,
[Column4] DATETIME NULL ,
[Column5] INT NULL ,
[Column6] VARCHAR(50) NULL ,
[Column7] VARCHAR(255) NULL ,
[Column8] INT NULL ,
[Column9] DATETIME NULL ,
[Column10] INT NULL ,
[Column11] INT NULL ,
[Column12] DATETIME NULL ,
[Column13] VARCHAR(50) NULL ,
[Column14] VARCHAR(50) NULL ,
[Column15] DATETIME NULL ,
[Column16] DATETIME NULL ,
[Column17] VARCHAR(8) NULL ,
[Column18] DATETIME NULL ,
[Column19] INT NULL ,
[Column20] INT NULL ,
[Column21] VARCHAR(50) NULL ,
[Column22] VARCHAR(255) NULL ,
[Column23] VARCHAR(50) NULL ,
[Column24] VARCHAR(255) NULL ,
[Column25] VARCHAR(50) NULL ,
[Column26] INT NULL ,
[Column27] INT NULL ,
[Column28] INT NULL ,
[Column29] INT NULL ,
[Column30] INT NULL ,
[Column31] INT NULL ,
[Column32] INT NULL ,
[Column33] INT NULL ,
[Column34] INT NULL ,
[Column35] VARCHAR(50) NULL ,
[Column36] VARCHAR(50) NULL ,
[Column37] VARCHAR(50) NULL ,
[Column38] VARCHAR(50) NULL ,
[Column39] VARCHAR(255) NULL ,
[Column40] INT NULL ,
[Column41] VARCHAR(50) NULL ,
[Column42] INT NULL ,
[Column43] VARCHAR(255) NULL ,
[Column44] INT NULL ,
[Column45] VARCHAR(255) NULL ,
[Column46] INT NULL ,
[Column47] DATETIME NULL ,
[Column48] DATETIME NULL ,
[Column49] DATETIME NULL ,
[Column50] INT NULL ,
[Column51] VARCHAR(50) NULL ,
[Column52] VARCHAR(255) NULL ,
[Column53] VARCHAR(50) NULL ,
[Column54] VARCHAR(255) NULL ,
[Column55] VARCHAR(50) NULL ,
[Column56] VARCHAR(255) NULL ,
[Column57] VARCHAR(50) NULL ,
[Column58] VARCHAR(50) NULL ,
[Column59] CHAR NULL ,
[Column60] CHAR NULL ,
[Column61] CHAR NULL ,
[Column62] CHAR NULL ,
[Column63] CHAR NULL ,
[Column64] CHAR NULL ,
[Column65] CHAR NULL ,
[Column66] CHAR NULL ,
[Column67] CHAR NULL ,
[Column68] CHAR NULL ,
[Column69] CHAR NULL ,
[Column70] CHAR NULL ,
[Column71] CHAR NULL ,
[Column72] CHAR NULL ,
[Column73] CHAR NULL ,
[Column74] CHAR NULL ,
[Column75] CHAR NULL ,
[Column76] DATETIME NULL ,
[Column77] INT NULL ,
[Column78] INT NULL ,
[Column79] VARCHAR(50) NULL ,
[Column80] VARCHAR(255) NULL ,
[Column81] VARCHAR(50) NULL ,
[Column82] VARCHAR(255) NULL ,
[Column83] VARCHAR(50) NULL ,
[Column84] VARCHAR(255) NULL ,
[Column85] VARCHAR(50) NULL ,
[Column86] VARCHAR(255) NULL ,
[Column87] VARCHAR(50) NULL ,
[Column88] VARCHAR(255) NULL ,
[Column89] VARCHAR(50) NULL ,
[Column90] VARCHAR(255) NULL ,
[Column91] VARCHAR(50) NULL ,
[Column92] VARCHAR(255) NULL ,
[Column93] VARCHAR(50) NULL ,
[Column94] VARCHAR(255) NULL ,
[Column95] VARCHAR(50) NULL ,
[Column96] VARCHAR(255) NULL ,
[Column97] VARCHAR(50) NULL ,
[Column98] VARCHAR(255) NULL ,
[Column99] VARCHAR(50) NULL ,
[Column100] VARCHAR(255) NULL ,
[Column101] VARCHAR(50) NULL ,
[Column102] VARCHAR(255) NULL ,
[Column103] VARCHAR(50) NULL ,
[Column104] VARCHAR(255) NULL ,
[Column105] VARCHAR(50) NULL ,
[Column106] VARCHAR(255) NULL ,
[Column107] VARCHAR(50) NULL ,
[Column108] VARCHAR(50) NULL ,
[Column109] VARCHAR(50) NULL ,
[Column110] VARCHAR(255) NULL ,
[Column111] VARCHAR(50) NULL ,
[Column112] VARCHAR(255) NULL ,
[Column113] VARCHAR(50) NULL ,
[Column114] VARCHAR(255) NULL ,
[Column115] VARCHAR(50) NULL ,
[Column116] VARCHAR(255) NULL ,
[Column117] VARCHAR(50) NULL ,
[Column118] VARCHAR(255) NULL ,
[Column119] VARCHAR(50) NULL ,
[Column120] VARCHAR(50) NULL ,
[Column121] VARCHAR(255) NULL ,
[Column122] VARCHAR(50) NULL ,
[Column123] VARCHAR(255) NULL ,
[Column124] VARCHAR(50) NULL ,
[Column125] VARCHAR(255) NULL ,
[Column126] VARCHAR(50) NULL ,
[Column127] VARCHAR(255) NULL ,
[Column128] VARCHAR(50) NULL ,
[Column129] VARCHAR(255) NULL ,
[Column130] VARCHAR(50) NULL ,
[Column131] VARCHAR(255) NULL ,
[Column132] DATETIME NULL ,
[Column133] VARCHAR(50) NULL ,
[Column134] VARCHAR(255) NULL ,
[Column135] VARCHAR(50) NULL ,
[Column136] INT NULL ,
[Column137] VARCHAR(50) NULL ,
[Column138] VARCHAR(255) NULL ,
[Column139] VARCHAR(50) NULL ,
[Column140] VARCHAR(255) NULL ,
[Column141] VARCHAR(50) NULL ,
[Column142] VARCHAR(255) NULL ,
[Column143] VARCHAR(50) NULL ,
[Column144] VARCHAR(255) NULL ,
[Column145] VARCHAR(50) NULL ,
[Column146] VARCHAR(255) NULL ,
[Column147] VARCHAR(50) NULL ,
[Column148] VARCHAR(255) NULL ,
[Column149] VARCHAR(50) NULL ,
[Column150] VARCHAR(255) NULL ,
[Column151] VARCHAR(50) NULL ,
[Column152] VARCHAR(255) NULL ,
[Column153] VARCHAR(50) NULL ,
[Column154] VARCHAR(255) NULL ,
[Column155] VARCHAR(50) NULL ,
[Column156] VARCHAR(255) NULL ,
[Column157] VARCHAR(50) NULL ,
[Column158] VARCHAR(255) NULL ,
[Column159] INT NULL ,
[Column160] INT NULL ,
[Column161] VARCHAR(50) NULL ,
[Column162] VARCHAR(50) NULL ,
[Column163] VARCHAR(50) NULL ,
[Column164] VARCHAR(50) NULL ,
[Column165] VARCHAR(50) NULL ,
[Column166] VARCHAR(50) NULL ,
[Column167] VARCHAR(50) NULL ,
[Column168] VARCHAR(50) NULL ,
[Column169] VARCHAR(255) NULL ,
[Column170] INT NULL ,
[Column171] VARCHAR(50) NULL ,
[Column172] INT NULL ,
[Column173] VARCHAR(50) NULL ,
[Column174] VARCHAR(50) NULL ,
[Column175] VARCHAR(50) NULL ,
[Column176] VARCHAR(255) NULL ,
[Column177] VARCHAR(50) NULL ,
[Column178] VARCHAR(255) NULL ,
[Column179] VARCHAR(50) NULL ,
[Column180] VARCHAR(50) NULL ,
[Column181] VARCHAR(50) NULL ,
[Column182] VARCHAR(255) NULL ,
[Column183] VARCHAR(50) NULL ,
[Column184] VARCHAR(255) NULL ,
[Column185] VARCHAR(50) NULL ,
[Column186] VARCHAR(255) NULL ,
[Column187] VARCHAR(50) NULL ,
[Column188] VARCHAR(255) NULL ,
[Column189] VARCHAR(50) NULL ,
[Column190] VARCHAR(50) NULL ,
[Column191] VARCHAR(50) NULL ,
[Column192] VARCHAR(255) NULL ,
[Column193] VARCHAR(50) NULL ,
[Column194] VARCHAR(255) NULL ,
[Column195] VARCHAR(50) NULL ,
[Column196] VARCHAR(50) NULL ,
[Column197] VARCHAR(255) NULL ,
[Column198] INT IDENTITY (1,1) ,
[Column199] VARCHAR(500) NULL ,
[Column200] VARCHAR(255) NULL ,
[Column201] VARCHAR(50) NULL ,
[Column202] VARCHAR(255) NULL ,
[Column203] CHAR NULL ,
[Column204] CHAR NULL ,
[Column205] VARCHAR(50) NULL ,
[Column206] VARCHAR(255) NULL ,
[Column207] BIGINT NULL ,
[Column208] VARCHAR(50) NULL ,
[Column209] VARCHAR(50) NULL ,
[Column210] VARCHAR(50) NULL ,
[Column211] VARCHAR(255) NULL ,
[Column212] VARCHAR(50) NULL ,
[Column213] VARCHAR(255) NULL ,
[Column214] VARCHAR(50) NULL ,
[Column215] VARCHAR(50) NULL ,
[Column216] VARCHAR(50) NULL ,
[Column217] VARCHAR(50) NULL ,
[Column218] VARCHAR(50) NULL ,
[Column219] VARCHAR(50) NULL ,
[Column220] VARCHAR(50) NULL ,
[Column221] VARCHAR(50) NULL ,
[Column222] DATETIME NULL ,
[Column223] VARCHAR(50) NULL ,
[Column224] VARCHAR(50) NULL ,
[Column225] CHAR NULL ,
[Column226] CHAR NULL ,
[Column227] CHAR NULL ,
[Column228] CHAR NULL ,
[Column229] CHAR NULL ,
[Column230] CHAR NULL ,
[Column231] VARCHAR(50) NULL ,
[Column232] VARCHAR(50) NULL ,
[Column233] VARCHAR(50) NULL ,
[Column234] VARCHAR(255) NULL ,
[Column235] VARCHAR(50) NULL ,
[Column236] VARCHAR(50) NULL ,
[Column237] VARCHAR(255) NULL ,
[Column238] VARCHAR(50) NULL ,
[Column239] VARCHAR(255) NULL ,
[Column240] VARCHAR(50) NULL ,
[Column241] VARCHAR(255) NULL ,
[Column242] CHAR NULL ,
[Column243] CHAR NULL ,
[Column244] DATE NULL ,
[Column245] DATE NULL ,
[Column246] DATE NULL ,
[Column247] VARCHAR(50) NULL ,
[Column248] VARCHAR(255) NULL ,
[Column249] VARCHAR(50) NULL ,
[Column250] VARCHAR(255) NULL ,
[Column251] DATE NULL ,
[Column252] DATE NULL ,
CONSTRAINT [PKC_MyTableA] PRIMARY KEY CLUSTERED
(
[Column198] ASC
)
)
GO
CREATE TABLE [dbo].[MyTableB]
(
Column1 [INT] IDENTITY(1,1) NOT NULL,
Column2 [INT] NULL,
Column3 [VARCHAR](255) NOT NULL,
Column4 [VARCHAR](255) NULL,
Column5 [CHAR](1) NOT NULL,
Column6 [VARCHAR](MAX) NULL,
Column7 [VARCHAR](50) NULL,
CONSTRAINT [PK_MyTableB] PRIMARY KEY CLUSTERED
(
Column3 ASC
)
)
GO
Run Code Online (Sandbox Code Playgroud)
插入一些虚拟数据:
DECLARE @valsSQL NVARCHAR(MAX) = 'SET IDENTITY_INSERT MyTableA ON;
INSERT INTO [MyTableA] ('
SELECT @valsSQL += c.name + ','
FROM sys.columns c
JOIN sys.tables t
ON c.object_id = t.object_id
WHERE t.name = 'MyTableA'
ORDER BY column_id
SET @valsSQL = STUFF(@valsSQL,LEN(@valsSQL),1,')')
SET @valsSQL += ' VALUES ( '
SELECT @valsSql +=
CASE
WHEN c.system_type_id = 167 OR --varchar
c.system_type_id = 175 -- char
THEN '''' + REPLICATE('a',c.max_length) + ''''
WHEN c.system_type_id = 61
THEN '''' + CONVERT(NVARCHAR,GETDATE(),120) + ''''
WHEN c.system_type_id = 56 OR --int OR
c.system_type_id = 47 OR -- bigint
c.system_type_id = 127
THEN CONVERT(NVARCHAR(10),CONVERT(INT,FLOOR(RAND()*2147483647)))
WHEN c.system_type_id = 40
THEN '''' + '1900-01-01' + ''''
END + ','
FROM sys.columns c
JOIN sys.types t
ON c.system_type_id = t.system_type_id
WHERE OBJECT_NAME(object_id) = 'MyTableA'
ORDER BY column_id
SET @valsSQL = STUFF(@valsSQL,LEN(@valsSQL),1,')')
SET @valsSQL += '; SET IDENTITY_INSERT MyTableA OFF;'
EXEC sp_executesql @stmt = @valsSQL
GO 500
Run Code Online (Sandbox Code Playgroud)
现在数据库已设置完毕,运行查询:
USE NO_PLAN
SELECT 1
-- my unique text to find this query in query store views
FROM MyTableA
INNER JOIN MyTableB Alias
ON Alias.Column3 = 'value'
LEFT JOIN MyTableB
ON MyTableB.Column3 = 'value'
WHERE MyTableB.Column4 IS NULL
Run Code Online (Sandbox Code Playgroud)
注意 - 实际的执行计划在这里
使用查询存储 DMV 获取查询 id 和计划 id,以便我们可以强制执行计划:
SELECT t.query_sql_text,
q.query_id,
p.plan_id,
p.query_plan,
p.is_forced_plan,
p.last_force_failure_reason_desc,
p.last_execution_time
FROM sys.query_store_plan p
JOIN sys.query_store_query q
ON q.query_id = p.query_id
JOIN sys.query_store_query_text t
ON t.query_text_id = q.query_text_id
WHERE t.query_sql_text LIKE '%-- my unique text to find this query in query store views%' AND
t.query_sql_text NOT LIKE '%sys.query_store_plan%' /* exclude this query */
Run Code Online (Sandbox Code Playgroud)
我的输出如下:
现在强制 SQL Server 在每次运行此查询时使用它刚刚编译的计划
EXEC sp_query_store_force_plan @query_id = 6, @plan_id = 6
Run Code Online (Sandbox Code Playgroud)
再次运行查询:
USE NO_PLAN
SELECT 1
-- my unique text to find this query in query store views
FROM MyTableA
INNER JOIN MyTableB Alias
ON Alias.Column3 = 'value'
LEFT JOIN MyTableB
ON MyTableB.Column3 = 'value'
WHERE MyTableB.Column4 IS NULL
Run Code Online (Sandbox Code Playgroud)
检查查询存储 DMV 以查看它是否使用了该计划:
SELECT t.query_sql_text,
q.query_id,
p.plan_id,
p.query_plan,
p.is_forced_plan,
p.last_force_failure_reason_desc,
p.last_execution_time
FROM sys.query_store_plan p
JOIN sys.query_store_query q
ON q.query_id = p.query_id
JOIN sys.query_store_query_text t
ON t.query_text_id = q.query_text_id
WHERE t.query_sql_text LIKE '%-- my unique text to find this query in query store views%' AND
t.query_sql_text NOT LIKE '%sys.query_store_plan%' /* exclude this query */
Run Code Online (Sandbox Code Playgroud)
我们可以看到NO_PLAN的失败原因:
如果我通过截断表、清除查询存储然后仅向表中添加 20 行(或者删除数据库并运行上述所有设置但使用而GO 20
不是GO 500
)来重置内容:
USE NO_PLAN;
ALTER DATABASE NO_PLAN SET QUERY_STORE CLEAR;
TRUNCATE TABLE [MyTableA];
DECLARE @valsSQL NVARCHAR(MAX) = 'SET IDENTITY_INSERT MyTableA ON;
INSERT INTO [MyTableA] ('
SELECT @valsSQL += c.name + ','
FROM sys.columns c
JOIN sys.tables t
ON c.object_id = t.object_id
WHERE t.name = 'MyTableA'
ORDER BY column_id
SET @valsSQL = STUFF(@valsSQL,LEN(@valsSQL),1,')')
SET @valsSQL += ' VALUES ( '
SELECT @valsSql +=
CASE
WHEN c.system_type_id = 167 OR --varchar
c.system_type_id = 175 -- char
THEN '''' + REPLICATE('a',c.max_length) + ''''
WHEN c.system_type_id = 61
THEN '''' + CONVERT(NVARCHAR,GETDATE(),120) + ''''
WHEN c.system_type_id = 56 OR --int OR
c.system_type_id = 47 OR -- bigint
c.system_type_id = 127
THEN CONVERT(NVARCHAR(10),CONVERT(INT,FLOOR(RAND()*2147483647)))
WHEN c.system_type_id = 40
THEN '''' + '1900-01-01' + ''''
END + ','
FROM sys.columns c
JOIN sys.types t
ON c.system_type_id = t.system_type_id
WHERE OBJECT_NAME(object_id) = 'MyTableA'
ORDER BY column_id
SET @valsSQL = STUFF(@valsSQL,LEN(@valsSQL),1,')')
SET @valsSQL += '; SET IDENTITY_INSERT MyTableA OFF;'
EXEC sp_executesql @stmt = @valsSQL
GO 20
Run Code Online (Sandbox Code Playgroud)
然后再次运行查询,我得到了不同的计划(注意过滤器运算符的位置已更改)
如果我随后重复获取 query_id 和 plan_id 的过程,强制执行计划并重新运行查询,这次它将强制执行计划:
我可以确认 NO_PLAN 计划不能通过提示强制执行OPTION (RECOMPILE, USE PLAN N'<planxmlhere>')
,我明白了
消息 8698,级别 16,状态 0,第 5 行 查询处理器无法生成查询计划,因为 USE PLAN 提示包含无法验证为查询合法的计划。删除或替换使用计划提示。为了获得计划强制成功的最佳可能性,请验证 USE PLAN 提示中提供的计划是否是 SQL Server 针对同一查询自动生成的计划。
许多文章表明 NO_PLAN 失败的原因是由于更改了索引,但是,从上面的示例可以看出,在强制执行查询和第二次运行查询之间没有任何变化。
为什么 SQL Server 在没有任何改变的情况下不能被迫使用它刚刚生成的计划?第一个计划是什么导致强制失败,为什么这对第二个计划来说不是问题?
并非 SQL Server 可以生成的每个计划都能够被强制,如错误消息所示(已添加强调):
消息 8698,级别 16,状态 0,行 xxx
查询处理器无法生成查询计划,因为 USE PLAN 提示包含无法验证为查询合法的计划。删除或替换使用计划提示。为了获得计划强制成功的最佳可能性,请验证 USE PLAN 提示中提供的计划是否是 SQL Server 针对同一查询自动生成的计划。
这是由所提供的 xml引导计划搜索方式的结果。SQL Server 使用该指南来选择可能产生所提供的运算符和属性的转换规则。当一切顺利时,将生成一个与所提供的 xml 表示具有相同主要功能的计划,尽管它可能在次要细节上有所不同,例如过滤器和计算标量放置:
此功能强制生成的执行计划将与强制执行的计划相同或相似。由于最终的计划可能与计划指南指定的计划不同,因此计划的性能可能会有所不同。在极少数情况下,性能差异可能显着且为负;在这种情况下,管理员必须删除强制计划。
SQL Server 仍然会经历与查找原始计划非常相似的过程。这是一个逐步将转换和替换应用于原始逻辑树表示的过程,同时考虑到暗示的计划形状。在这些步骤中,很多事情都可能出错,这意味着 SQL Server 最终不会接近预期的终点。
以下是引导式搜索的总体概述:
我提到这一切是因为人们通常不理解 xml 是内部可执行计划的表示,而不是计划本身。SQL Server 无法直接将 xml 转换为所有正确的内部结构。它必须使用 xml 作为粗略指南来经历搜索过程。
我想说的一点是,无论文档如何规定,计划强制并不是一个具有最小(且完整记录的)故障模式的精确过程。最初的计划指南并未得到广泛使用,失败很容易被解释(或挥手)。查询存储及其计划指导版本的日益普及,正在提高此功能的总体体验水平。
随着语句依赖于查询优化器转换的更复杂的交互,失败的可能性会增加。您的示例查询很好地说明了这一点,因为它在外连接/连接切换上采用了复杂的选择:
SelOJJoinSwitch - Sel((A JN B) OJ C) -> (Sel(A OJ C)) JN B
Run Code Online (Sandbox Code Playgroud)
这种复杂的重写可能(总是)不能很好地与引导搜索配合使用,因为它通常不会考虑已经指出的过滤器位置。
在只有 20 行的第二个示例中,由于预期计划成本较低,优化器的计划搜索在搜索 0 (事务处理)阶段后结束。搜索 0 不允许SelOJJoinSwitch规则。由于不运行该探索,过滤器(选择)在计划中的放置有所不同。
如果使用未记录的跟踪标志 8750 禁用搜索 0,您将获得与 20 行测试相同的不可强制计划。
原始 SQL 也是查询要求的一种奇怪的表达方式,交叉联接伪装成内部联接,并在其子句中对一个表进行选择ON
。
可以通过以下等效重写来强制提供的计划:
-- Rewrite 1
SELECT 1
FROM dbo.MyTableA, dbo.MyTableB AS Alias
LEFT JOIN dbo.MyTableB ON MyTableB.Column3 = 'value'
WHERE dbo.MyTableB.Column4 IS NULL
-- Rewrite 2
SELECT 1
FROM dbo.MyTableA
CROSS JOIN
(
dbo.MyTableB AS Alias
LEFT JOIN dbo.MyTableB
ON MyTableB.Column3 = 'value'
)
WHERE dbo.MyTableB.Column4 IS NULL
-- Rewrite 3
SELECT 1
FROM MyTableA
JOIN
(
MyTableB Alias
LEFT JOIN MyTableB ON MyTableB.Column3 = 'value'
) ON Alias.Column3 = 'value'
WHERE
MyTableB.Column4 IS NULL
Run Code Online (Sandbox Code Playgroud)
我使用了一个USE PLAN
提示,但是任何这些重写都会生成相同的计划,可以使用查询存储强制执行。
由于绑定和优先规则,括号在CROSS JOIN
和变体中很重要。INNER JOIN
从语句文本派生的初始逻辑树需要从使最终计划可达的点开始。这三个是此类起点的示例,而您的原创则不是。
SQL Server 2022包含有限的优化器规则重播功能,因此计划指导的可靠性可能会提高。这可能会或可能不会优于基于计划的 xml 表示中发现的功能的指导。
最后,像这样的指导失败总是有可能是由于产品缺陷造成的。您需要联系 Microsoft 以获得明确的答案(尽管您仍然可能得不到)。如果是缺陷,很可能涉及SelOJJoinSwitch规则。
以下重现了该问题:
DECLARE @A table (c1 integer NULL);
DECLARE @B table (c1 integer NULL);
DECLARE @C table (c1 integer NULL);
SELECT (SELECT 1)
FROM @A AS A
CROSS JOIN @B AS B
LEFT JOIN @C AS C
ON C.c1 = B.c1
WHERE C.c1 IS NULL;
Run Code Online (Sandbox Code Playgroud)
这将产生一个使用提示无法强制执行的计划USE PLAN
。用常量 1替换子查询会生成在搜索 0(SELECT 1)
阶段生成的强制计划。或者,禁用该规则也会产生一个强制计划。OPTION (QUERYRULEOFF SelOJJoinSwitch)
上面的重写也适用于最小的例子:
DECLARE @A table (c1 integer NULL);
DECLARE @B table (c1 integer NULL);
DECLARE @C table (c1 integer NULL);
SELECT (SELECT 1)
FROM @A AS A, @B AS B -- Changed cross join syntax
LEFT JOIN @C AS C
ON C.c1 = B.c1
WHERE C.c1 IS NULL;
Run Code Online (Sandbox Code Playgroud)
这会产生一个具有相同结构的强制计划,而不涉及SelOJJoinSwitch: