当数据库强制加入时,如何强制执行更好的执行计划?
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
列上添加索引,并在num
和tid
上添加索引。服务器没有选择任何索引。
我希望减少页面读取次数,但仍会检索 id
和 num
列。
【问题讨论】:
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
上有一个非聚集索引,您将查询使用过滤数据的数据非聚集索引并获取 id
和 num
列。
所以SQL server会使用Non Clustered Index来过滤数据(t_id=587
),但是过滤数据后SQL server需要获取存储在id
和num
列中的值。显然因为你有聚集索引,那么 SQL 服务器将使用这个索引来获取存储在 id
和 num
列中的数据。发生这种情况是因为非聚集索引树中的叶子包含聚集索引的值,这就是您在执行计划中看到键查找运算符的原因。事实上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 查找数据并返回 id
和 num
列时,它不需要进行 Key Lookup!
最后我们需要做的,是在非聚集索引中INCLUDE
num
!感谢@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执行计划使用并行提升在复杂查询语句下的性能
我们如何在 GitHub 中强制执行强制审查,但仍允许从 CI 发布 Maven?