当数据库强制加入时,如何强制执行更好的执行计划?

Posted

技术标签:

【中文标题】当数据库强制加入时,如何强制执行更好的执行计划?【英文标题】: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 性能。 (约束、索引和计划对性能至关重要。) 【参考方案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 欢迎您。如果您觉得它有帮助,请也给答案投票 ;-)

以上是关于当数据库强制加入时,如何强制执行更好的执行计划?的主要内容,如果未能解决你的问题,请参考以下文章

强制SQL Server执行计划使用并行提升在复杂查询语句下的性能

当 JPA 插入继承的对象时强制执行 FK 约束

我们如何在 GitHub 中强制执行强制审查,但仍允许从 CI 发布 Maven?

如何强制子查询像 #temp 表一样执行?

当本地git repo执行时,GitHub不强制执行.gitattributes

PostgreSQL 手动更改查询执行计划以强制使用排序和顺序访问而不是全扫描