将表提示 INDEX 和 FORCESEEK 与两个不在 PK 上的连接相结合

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 字段

ype*_*eᵀᴹ 9

我还将测试这种重写(假设这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)

  • 我认为使用提示不是一个好主意。除非您有性能问题并且您确定执行计划不是最好的并且您没有其他方法来解决它(重写、不同的索引等)。 (4认同)

Pau*_*ite 6

如何使第一个查询编译?

坏消息是,由于 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子句谓词拒绝任何NULLsLEFT JOIN. 称为简化规则SimplifyLOJN将过滤器 + 左外连接转换为内连接:

简化后LOJN

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

在 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 建议重写的查询计划(尽管“理想”计划的确切形状将取决于数据量和分布,但我希望您能理解我的一般观点)。


孔夫子*_*孔夫子 5

您的查询存在重大问题。答案仍然正确的唯一原因是使用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对抗另一个。

  • 理查德 我已经编辑了您出色且有用的帖子和评论,使它们更加“社区友好”。如果您对我所做的更改有任何异议,我很乐意在 [站点聊天室](http://chat.stackexchange.com/rooms/179/the-heap) 中与您讨论这个问题。感谢您在这里做出贡献,请继续努力:) (4认同)