【问题标题】:How to I force a better execution plan when the database is forcing a join?当数据库强制加入时,如何强制执行更好的执行计划?
【发布时间】:2019-11-07 19:58:12
【问题描述】:

我正在优化 SQL Server 2005 上的查询。我有一个针对 mytable 的简单查询,它有大约 200 万行:

SELECT id, num
FROM mytable
WHERE t_id = 587

id 字段是主键(聚集索引),t_id 字段上存在非聚集索引。

上述查询的查询计划包括聚集索引查找和索引查找,然后执行嵌套循环(内连接)以组合结果。 STATISTICS IO 显示 3325 页读取。

如果我将查询更改为以下内容,则服务器仅执行 6 次页面读取,并且仅执行一个没有连接的索引搜索:

SELECT id
FROM mytable
WHERE t_id = 587

我尝试在num 列上添加索引,并在numtid 上添加索引。服务器没有选择任何索引。

我希望减少页面读取次数,但仍会检索 idnum 列。

【问题讨论】:

  • 2005 年?认真的吗?
  • 请在代码问题中给出minimal reproducible example--剪切&粘贴&可运行代码;具有期望和实际输出的示例输入(包括逐字错误消息);标签和明确的规范和解释。对于包含 DBMS/产品和 DDL 的 SQL,其中包括约束、索引和基表初始化。对于包括 EXPLAIN & STATISTICS 结果的 SQL 性能。 (约束、索引和计划对性能至关重要。)

标签: sql-server tsql sql-server-2005 query-performance


【解决方案1】:

以下索引应该是最优的:

CREATE INDEX idx ON MyTable (t_id)
INCLUDE (num)

我不记得 INCLUDEd 列在 2005 年是否是有效语法,您可能必须使用:

CREATE INDEX idx ON MyTable (t_id, num)

[id] 列将包含在索引中,因为它是聚集键。

【讨论】:

  • 我看不出这是最优的。它仍然需要选择id
  • 聚集索引在[id]上,自动包含在所有索引中。
  • 这仍然意味着 2 个索引搜索和一个连接。
  • Ben,MJH 提供的第一个答案(带有INCLUDE 子句)完全符合我的需要,并且在 SQL Server 2005 上工作。我的查询现在作为具有 6 次读取的单个 Index Seek 执行。完美。
【解决方案2】:

最佳索引将位于(t_id, num, id)

您的查询可能是一个不好的方面的原因是因为选择了多行。我想知道这样改写查询是否会提高性能:

SELECT t.id, t.num
FROM mytable t
WHERE EXISTS (SELECT 1
              FROM my_table t2
              WHERE t2.t_id = 587 AND t2.id = t.id
             );

【讨论】:

    【解决方案3】:

    让我们澄清问题,然后讨论改进的解决方案:

    您有一个表(我们称之为 tblTest1 并包含 2M 条记录),在 id 上有一个聚集索引,在 t_id 上有一个非聚集索引,您将查询使用过滤数据的数据非聚集索引并获取 idnum 列。

    所以SQL server会使用Non Clustered Index来过滤数据(t_id=587),但是过滤数据后SQL server需要获取存储在idnum列中的值。显然因为你有聚集索引,那么 SQL 服务器将使用这个索引来获取存储在 idnum 列中的数据。发生这种情况是因为非聚集索引树中的叶子包含聚集索引的值,这就是您在执行计划中看到键查找运算符的原因。事实上SQL Server使用Index seek(NonCluster)找到t_id=587然后使用Key Lookup获取num数据!(SQL Server不会使用这个操作符来获取存储在id列中的值,因为您有一个聚集索引,并且非聚集索引中的叶子包含聚集索引的值)。

    参考上面的截图,当我们有Index Seek(NonClustred)Key Lookup时,SQL Server需要Nested Loop Join操作符来获取num列中的数据,使用Index Seek(Nonclustered)操作符。实际上在这个阶段SQL Server有两个独立的集合:一个是从Nonclustered Index树得到的结果,另一个是Clustered Index树里面的数据。

    根据这个故事,问题就很清楚了!如果我们对 SQL Server 说,而不是进行 Key Lookup,会发生什么?这将导致 SQL Server 使用更短的方式执行查询(不需要键查找,显然不需要嵌套循环连接!)。

    为此,我们需要INCLUDE NonClustered 索引树内的num 列,因此在这种情况下,该索引的叶子将包含id 列的数据以及num 列的数据!显然,当我们说 SQL Server 使用 NonClustred Index 查找数据并返回 idnum 列时,它不需要进行 Key Lookup!

    最后我们需要做的,是在非聚集索引中INCLUDEnum!感谢@MJH的回答:

    CREATE NONCLUSTERED INDEX idx ON tblTest1 (t_id)
    INCLUDE (num)
    

    幸运的是,SQL Server 2005 为 NonClustered 索引提供了一项新功能,能够在 NonClustered 索引的叶级包含额外的非键列!

    阅读更多:

    https://www.red-gate.com/simple-talk/sql/learn-sql-server/using-covering-indexes-to-improve-query-performance/

    https://docs.microsoft.com/en-us/sql/relational-databases/indexes/create-indexes-with-included-columns?view=sql-server-2017

    但是如果我们这样写查询会发生什么?

    SELECT id, num
    FROM tblTest1 AS t1
    WHERE 
    EXISTS (SELECT 1
                  FROM tblTest1 t2
                  WHERE t2.t_id = 587 AND t2.id = t1.id
                 )
    

    这是一个很好的方法,但让我们看看执行计划:

    显然,SQL server 需要做一次 Index seek(NonClustered) 来找到 t_id=587,然后使用 Clustered Index Seek 从 Clustered Index 中获取数据。在这种情况下,我们不会得到任何显着的性能改进。

    注意:当您使用索引时,您需要制定适当的计划来维护它们。随着索引的碎片化,它们对查询性能的影响会降低,一段时间后您可能会遇到性能问题!当它们支离破碎时,您需要制定适当的计划来重组和重建它们!

    阅读更多:https://docs.microsoft.com/en-us/sql/relational-databases/indexes/reorganize-and-rebuild-indexes?view=sql-server-2017

    【讨论】:

    • Vahid,很好的解释。当我提出最初的问题时,我对INCLUDE 和在索引中存储其他字段一无所知。在阅读了@MJH 提供的解决方案后,答案很明显,我已经将它部署在我的应用程序的其他几个关键部分中,以便在执行时间减少。您的解释强化了他的回答所暗示的内容,并扩展了我错过的一些事情……而且我主要感谢您为此付出的努力。谢谢!
    • @JoshuaOlson 欢迎您。如果您觉得它有帮助,请也给答案投票 ;-)
    猜你喜欢
    • 2010-11-05
    • 1970-01-01
    • 1970-01-01
    • 2010-12-21
    • 2011-05-31
    • 1970-01-01
    • 1970-01-01
    • 2019-05-18
    • 2010-10-22
    相关资源
    最近更新 更多