pap*_*zzo 4 sql-server-2008 sql-server hints
请参阅下面的第一个查询。
不能将表提示索引和 forceseek 与两个连接组合在一起,并且连接不在 PK 上。
如何使第一个查询编译?
有趣
- 如果只有一个连接或另一个可以组合索引和 forceseek 提示
- 如果索引是 PK 那么可以组合 2 个连接并有两个提示
-- compiler fails
-- Query processor could not produce a query plan because of the hints defined in this query. Resubmit the query without specifying any hints and without using SET FORCEPLAN.
Select Count(Distinct([docSVsys].[sID]))
From [docSVsys] with (nolock)
Left Join [docSVtext] with (nolock, Index(IX_docSVtext_value_sID), forceseek )
On [docSVtext].[sID] = [docSVsys].[sID]
Left Join [docMVtext] with (nolock, Index(ix_docMVtext_value_sID), forceseek)
On [docMVtext].[sID] = [docSVsys].[sID]
where [docSVtext].[value] = 'doug' or
[docMVtext].[value] = 'doug'
-- can do one join
Select Count(Distinct([docSVsys].[sID]))
From [docSVsys] with (nolock)
Left Join [docSVtext] with (nolock, Index(IX_docSVtext_value_sID), forceseek )
On [docSVtext].[sID] = [docSVsys].[sID]
where [docSVtext].[value] = 'doug'
-- can do the other join
Select Count(Distinct([docSVsys].[sID]))
From [docSVsys] with (nolock)
Left Join [docSVtext] with (nolock, Index(IX_docSVtext_value_sID), forceseek )
On [docSVtext].[sID] = [docSVsys].[sID]
where [docSVtext].[value] = 'doug'
-- if on the PK then can do forceseek on two join
Select Count(Distinct([docSVsys].[sID]))
From [docSVsys] with (nolock, INDEX(PK_docSVsys))
Left Join [docSVtext] with (nolock, Index(PK_docSVtext), forceseek )
On [docSVtext].[sID] = [docSVsys].[sID]
Left Join [docMVtext] with (nolock, Index(PK_docMVtext), forceseek)
On [docMVtext].[sID] = [docSVsys].[sID]
where [docSVtext].[value] = 'doug'
or [docMVtext].[value] = 'doug'
Run Code Online (Sandbox Code Playgroud)
这做我想要的并且运行速度提高了 100 倍。
这与 ypercube 的答案具有几乎相同的查询计划。
但是我不能将它与我需要的其他一些条件结合起来。
Select count(distinct([docSVsys].[sID]))
From [docSVsys] with (nolock)
LEFT HASH JOIN [docSVtext] with (nolock)
on [docSVtext].[sID] = [docSVsys].[sID]
and [docSVtext].[value] = 'doug'
LEFT HASH JOIN [docMVtext] with (nolock)
on [docMVtext].[sID] = [docSVsys].[sID]
and [docMVtext].[value] = 'doug'
where [docSVtext].[sID] is not null
or [docMVtext].[sID] is not null
Run Code Online (Sandbox Code Playgroud)
这也使用适当的索引并且是最快的。
现在我没有重写格式的选项。
该程序建立对其他表的查询。
我们错误地希望 where 作为 order by 的格式与我们所处的位置相同。
SELECT count(distinct(c.sID))
from
( SELECT sv.[sID]
FROM [docSVtext] AS sv
WHERE sv.[value] = 'doug'
UNION ALL
SELECT mv.[sID]
FROM [docMVtext] AS mv
WHERE mv.[value] = 'doug'
) as c
Run Code Online (Sandbox Code Playgroud)
docSVsys
sID Int PK Identity
docMVtext
sID Int PK FK to docSVsys
fieldID Int PK
value string PK
ix value, sID
docSVtext
sID Int PK FK to docSVsys
fieldID Int PK
value string
ix value, sID
MV 是多值文本
表 SV 是单值文本表
fieldID 是唯一字段
这是搜索所有 SV 和 MV 字段
我还将测试这种重写(假设这sID是 的主键docSVsys):
SELECT COUNT(*)
FROM [docSVsys] AS d
WHERE EXISTS
( SELECT *
FROM [docSVtext] AS sv
WHERE sv.[sID] = d.[sID]
AND sv.[value] = 'doug'
)
OR EXISTS
( SELECT *
FROM [docMVtext] AS mv
WHERE mv.[sID] = d.[sID]
AND mv.[value] = 'doug'
) ;
Run Code Online (Sandbox Code Playgroud)
如何使第一个查询编译?
坏消息是,由于 SQL Server 查询优化器的限制,今天无法做到这一点。
SELECT
COUNT_BIG(DISTINCT(dsv.[sid]))
FROM dbo.docSVsys AS dsv
LEFT JOIN dbo.docSVtext AS dst ON
dst.[sid] = dsv.[sid]
LEFT JOIN dbo.docMVtext AS dmt ON
dmt.[sid] = dsv.[sid]
WHERE
dst.value = 'doug'
OR dmt.value = 'doug';
Run Code Online (Sandbox Code Playgroud)
上述逻辑查询规范在语义上等同于EXISTS在ypercube提议的制剂答案和LEFT JOIN在wBob的修改作出回答。逻辑公式没有说明 SQL Server 应该如何物理执行查询。由于 ,结果是正确的(正如 Richard 所同意的那样)DISTINCT,但这并不意味着查询是“错误的”。给定一个具有所有可能转换和无限时间和资源的查询优化器,上面的文本可以产生与 ypercube 和 wBob 建议的文本完全相同的最佳执行计划。
作为解释,让我们看看 SQL Server 如何为您的第一个“可以做”的例子找到一个有效的物理计划:
SELECT
COUNT_BIG(DISTINCT(dsv.[sid]))
FROM dbo.docSVsys AS dsv
LEFT JOIN dbo.docSVtext AS dst ON
dst.[sid] = dsv.[sid]
WHERE
dst.value = 'doug';
Run Code Online (Sandbox Code Playgroud)
如果查询优化器只执行最明显的实现,则查询计划将是:

实际上,优化器执行了两个重要的重写。首先它看到WHERE子句谓词拒绝任何NULLs由LEFT JOIN. 称为简化规则SimplifyLOJN将过滤器 + 左外连接转换为内连接:

其次,优化重写(通过SID组)流聚合+内加入作为左半加入使用一个名为规则GbAggJNtoLSJN:

相同的事件序列适用于您的第二个示例(docMVtext我确信这意味着引用该表)。和以前一样,由于-rejecting谓词,theLEFT JOIN被简化为 an ,然后产生的聚合和组合被简化为 Left Semi Join。INNER JOINNULLWHEREDISTINCTINNER JOIN
如果优化器包含OR更全面地推理原始示例中的谓词的逻辑(还考虑DISTINCT),它可能会生成如下查询计划:

该计划的 T-SQL 公式接近UNION您可能想到的重写:
SELECT COUNT_BIG(*) FROM
(
SELECT dsv.[sid]
FROM dbo.docSVsys AS dsv
LEFT JOIN dbo.docSVtext AS dst ON
dst.[sid] = dsv.[sid]
WHERE
dst.value = 'doug'
UNION
SELECT dsv.[sid]
FROM dbo.docSVsys AS dsv
LEFT JOIN dbo.docMVtext AS dmt ON
dmt.[sid] = dsv.[sid]
WHERE
dmt.value = 'doug'
) AS SubQuery;
Run Code Online (Sandbox Code Playgroud)
如果优化器真正包含所有可能的重写,那么即使该公式也可以转换为更有效的形式:

这只是 ypercube 建议重写的查询计划(尽管“理想”计划的确切形状将取决于数据量和分布,但我希望您能理解我的一般观点)。
您的查询存在重大问题。答案仍然正确的唯一原因是使用DISTINCT. 否则,如果由于 3 个表之间的笛卡尔积而导致表很大,您会看到生成了一个巨大的结果集。
数据线
create table docSVsys(
sid int identity primary key clustered);
create table docSVtext(
id int identity primary key clustered,
sid int not null references docSVsys(sid),
value varchar(10));
create index IX_docSVtext_value_sID on docSVtext(value,sid);
create table docMVtext(
id int identity primary key clustered,
sid int not null references docSVsys(sid),
value varchar(10));
create index ix_docMVtext_value_sID on docMVtext(value,sid);
Run Code Online (Sandbox Code Playgroud)
一些样本数据
insert docSVsys default values; -- 1
insert docSVsys default values; -- 2
insert docSVtext values (1, 'doug');
insert docSVtext values (1, 'jim');
insert docSVtext values (2, 'jane');
insert docSVtext values (2, 'piglet');
insert docSVtext values (2, 'troy');
insert docMVtext values (1, 'pooh');
insert docMVtext values (1, 'eenie');
insert docMVtext values (1, 'meenie');
insert docMVtext values (1, 'minie');
insert docMVtext values (1, 'moe');
insert docMVtext values (1, 'earl');
insert docMVtext values (1, 'tigger');
insert docMVtext values (2, 'doug');
Run Code Online (Sandbox Code Playgroud)
您的查询- 注释掉提示,添加列并删除 COUNT(DISTINCT)
Select [docSVsys].[sID], docSVtext.value, docMVtext.value
From [docSVsys] with (nolock)
Left Join [docSVtext] --with (nolock, Index(IX_docSVtext_value_sID), forceseek )
On [docSVtext].[sID] = [docSVsys].[sID]
Left Join [docMVtext] --with (nolock, Index(ix_docMVtext_value_sID), forceseek)
On [docMVtext].[sID] = [docSVsys].[sID]
where [docSVtext].[value] = 'doug' or
[docMVtext].[value] = 'doug';
Run Code Online (Sandbox Code Playgroud)
结果集
sID value value
1 doug pooh
1 doug eenie
1 doug meenie
1 doug minie
1 doug moe
1 doug earl
1 doug tigger
2 jane doug
2 piglet doug
2 troy doug
Run Code Online (Sandbox Code Playgroud)
结论
您OR基本上是说,如果docSVtextSID+'doug' 上的左连接有效,请仅根据 SID给我所有记录docMVtext。尽管这DISTINCT将使其成为无操作,但 SQL Server 无法优化此路径,因为在另一个SID值上,它可能docMVtext成功匹配 'doug',因此 SQL Server 需要下降两个连接路径。
当 SQL Server 说您的提示没有意义时,在这种情况下是因为它们没有意义。它不能使用针对任何一个LEFT JOIN-ed 表的 'doug' 值作为索引的引导,因此它必须基本上挖掘第一列(值)上的每个索引键以到达第二列(sid)来执行seek. 但是,由于or条件,在 2 LEFT JOIN-ed 表之间没有合法的主机,因此两者都不能用于seek对抗另一个。