为什么关系数据库中的表联接顺序不重要

照片由 泛李孝贞 Unsplash

我向我提出了一个很大的问题,我认为这将使一个好的SQL Server博客文章变得有意义:

…我一直在想,从我开始查询的性能角度来看,这真的很重要吗? 例如,如果我从ABC加入,那么从 B开始到A&C 会更好 吗?

简短的回答:是的。 和不。

观察者多于读者? 在YouTube上 观看本周的 剧集

表连接顺序对性能至关重要!

免责声明:对于这篇文章,我只会谈论INNER联接。 外(左,右,全等)联接是一种完整的'另一只动物,我会节省时间。

让我们使用来自WideWorldImporters的以下查询作为示例:

/* 
-- Run if if you want to follow along - add a computed column and index for CountryOfManufacture
ALTER TABLE Warehouse.StockItems SET (SYSTEM_VERSIONING = OFF);
ALTER TABLE Warehouse.StockItems
ADD CountryOfManufacture AS CAST(JSON_VALUE(CustomFields,'$.CountryOfManufacture') AS NVARCHAR(10))
ALTER TABLE Warehouse.StockItems SET (SYSTEM_VERSIONING = ON);
CREATE INDEX IX_CountryOfManufacture ON Warehouse.StockItems (CountryOfManufacture)
*/

SELECT
o.OrderID,
s.CountryOfManufacture
FROM
Sales.Orders o -- 73595 rows
INNER JOIN Sales.OrderLines l -- 231412 rows
ON o.OrderID = l.OrderID -- 231412 rows after join
INNER JOIN Warehouse.StockItems s -- 227 rows
ON l.StockItemID = s.StockItemID -- 1036 rows after join
AND s.CountryOfManufacture = 'USA' -- 8 rows for USA

注意:对于INNER联接,我通常希望将“ USA”过滤器放在WHERE子句中,但是对于其余这些示例,将其包含在ON中会更容易。

需要注意的关键是,我们要联接三个表-Order,OrderLines和StockItems,而OrderLines是我们用来联接其他两个表的表。

对于表连接订单,我们基本上有两个选择-我们可以先将OrderLines与OrderLines联接,然后再加入StockItems,或者我们可以先将OrderLines和StockItems联接,再加入Orders。

在性能方面,几乎可以肯定的是,后一种情况(首先将OrderLines与StockItems结合使用)会更快,因为StockItems将帮助我们进行更多选择。

可选择的? 您可能会注意到我们的StockItems表很小,只有227行。 通过过滤“ USA”将其缩小到只有8行,甚至变得更小。

由于StockItems表没有重复的行(这是用于产品信息的简单查找表),因此是一个很好的表,可以尽早加入,因为这将减少在其余查询中传递的总行数。

如果我们尝试先执行Orders to OrderLines连接,则实际上我们不会在第一步中过滤掉任何行,导致随后的StockItems连接更加慢(因为必须处理更多行)。

基本上,连接顺序确实很重要,因为如果我们可以连接两个表,这将减少后续步骤需要处理的行数,那么我们的性能将会提高。

因此,如果出于性能原因, 联接表的顺序有很大不同,那么SQL Server遵循我们定义的联接顺序吧?

SQL Server不允许您选择连接顺序

SQL是一种声明性语言:您编写的代码指定要获取的是什么数据,而不是如何获取的。

基本上,SQL Server查询优化器接受您的SQL查询并自行决定它认为应该如何获取数据。

它通过使用有关表大小和数据内容的预先计算的统计信息来执行此操作,以便能够快速选择“足够好”的计划。

因此,即使我们像这样在FROM语句中重新排列表的顺序,也是如此:

SELECT
o.OrderID,
s.CountryOfManufacture
FROM
Sales.OrderLines l
INNER JOIN Warehouse.StockItems s
ON l.StockItemID = s.StockItemID
AND s.CountryOfManufacture = 'USA'
INNER JOIN Sales.Orders o
ON o.OrderID = l.OrderID

或者,如果我们加上括号:

SELECT
o.OrderID,
s.CountryOfManufacture
FROM
(Sales.OrderLines l
INNER JOIN Sales.Orders o
ON l.OrderID = o.OrderID)
INNER JOIN Warehouse.StockItems s
ON l.StockItemID = s.StockItemID
AND s.CountryOfManufacture = 'USA'

或者即使我们将表重写为子查询:

SELECT
l.OrderID,
s.CountryOfManufacture
FROM
(
SELECT
o.OrderID,
l.StockItemId
FROM
Sales.OrderLines l
INNER JOIN Sales.Orders o
ON l.OrderID = o.OrderID
) l
INNER JOIN Warehouse.StockItems s
ON l.StockItemID = s.StockItemID
AND s.CountryOfManufacture = 'USA'

SQL Server将把我们的三个独立查询(以及页面顶部的原始查询)解释和优化为相同的确切执行计划:

基本上,无论我们如何尝试在FROM语句中重新定义表的顺序,SQL Server仍将尽其所能。

但是,如果SQL Server最不了解该怎么办?

在大多数情况下,我看到SQL Server的执行计划效率低下,通常是由于该表/索引的统计信息有误。

统计信息也是博客帖子“整个”一天(或整个月)的“另一个主题”,因此,不要太过分关注此帖子,我将向您介绍Kimberly Tripp关于该主题的介绍性博客帖子: https: //www.sqlskills.com/blogs/kimberly/the-accidental-dba-day-15-of-30-statistics-maintenance/

要带走的关键是,如果SQL Server生成的执行计划中表联接的顺序没有意义, 请首先检查您的统计信息,因为它们是造成许多性能问题的根本原因!

强制加入订单

因此,您已经检查过统计信息是否出了问题,并在那方面穷尽了所有可能性。 SQL Server并未针对最佳表连接顺序进行优化,那么您该怎么办?

行目标

如果SQL Server行为不佳,并且我需要强制执行表连接顺序,则我的首选方法是通过TOP()命令来执行。

通过观看亚当·马汉尼奇(Adam Machanic)关于该主题的精彩演讲,我学到了这项技术,我强烈建议您观看。

由于在我们的示例查询中,SQL Server已经以最有效的顺序联接表,因此,我们首先将Orders与OrderLines联接,以强制进行低效的联接。

基本上,我们围绕要首先连接在一起的表编写子查询,并确保包含TOP子句。

SELECT
o.OrderID,
s.CountryOfManufacture
FROM
(
SELECT TOP(2147483647) -- A number of rows we know is larger than our table. Watch Adam's presentation above for more info.
o.OrderID,
l.StockItemID
FROM
Sales.Orders o
INNER JOIN Sales.OrderLines l
ON o.OrderID = l.OrderID
) o
INNER JOIN Warehouse.StockItems s
ON o.StockItemID = s.StockItemID
AND s.CountryOfManufacture = 'USA'

包含TOP会强制SQL首先执行Orders和OrderLines之间的联接-在此示例中效率低下,但是在能够控制SQL Server的功能方面取得了巨大成功。

这是强制执行连接顺序的我最喜欢的方式,因为在这种情况下,我们可以对两个特定表的连接顺序(Orders和OrderLines)注入控制权,但是SQL Server仍然会自行决定如何连接其余表。

虽然强行执行连接顺序通常是一个坏主意(如果将来基础数据发生更改并且您的强行连接不再是最佳选择,会发生什么情况),但是在某些情况下,需要使用TOP技术将导致最少的性能问题(因为SQL仍然可以决定其余表的处理方式)。

如果使用提示则无法说相同的话…

查询和加入提示

查询和联接提示将成功强制查询中表联接的顺序,但是它们有很多缺点。

让我们看一下FORCE ORDER查询提示。 将其添加到查询中将成功强制表连接按照列出的顺序进行:

SELECT
o.OrderID,
s.CountryOfManufacture
FROM
Sales.Orders o
INNER JOIN Sales.OrderLines l
ON o.OrderID = l.OrderID
INNER JOIN Warehouse.StockItems s
ON l.StockItemID = s.StockItemID
AND s.CountryOfManufacture = 'USA'
OPTION (FORCE ORDER)

查看执行计划,我们可以看到Orders和OrderLines首先按预期方式连接在一起:

FORCE ORDER提示的最大缺点是查询中的所有表都将强制执行其连接顺序(在此示例中不明显……但是假设我们总共要连接4或5个表)。

这使您的查询变得异常脆弱。 如果基础数据将来发生更改,则可能会强制多个低效的联接顺序。 您使用FORCE ORDER调整的查询的运行时间可能从几秒钟缩短到几分钟或几小时。

使用联接提示存在相同的问题:

SELECT
o.OrderID,
s.CountryOfManufacture
FROM
Sales.Orders o
INNER LOOP JOIN Sales.OrderLines l
ON o.OrderID = l.OrderID
INNER JOIN Warehouse.StockItems s
ON l.StockItemID = s.StockItemID
AND s.CountryOfManufacture = 'USA'

使用LOOP提示成功地再次强制了我们的联接顺序,但是所有表的联接顺序又一次变得固定了:

连接提示可能是强制表连接顺序的最脆弱的提示,因为它不仅强制连接顺序,而且还强制执行连接的算法。

通常,我仅使用查询提示来强制表连接顺序作为临时修订

也许生产有问题,我需要重新运行。 查询或联接提示可能是解决当前问题的最快方法。 但是,长期使用提示可能不是一个好主意,因此,在立即扑灭火灾之后,我将返回并尝试确定性能问题的根本原因。

摘要

  • 表连接顺序对于减少查询其余部分需要处理的行数很重要。
  • 默认情况下,SQL Server无法控制连接顺序-它使用统计信息和查询优化器来选择认为良好的连接顺序。
  • 大多数时候,查询优化器在选择有效的联接顺序方面做得很好。 如果不是这样,我要做的第一件事就是检查统计信息的运行状况,并弄清它是否因此而选择了次优计划。
  • 如果我处于特殊情况下并且确实需要强制执行连接顺序,则将使用TOP子句强制执行连接顺序,因为它仅强制执行单个连接的顺序。
  • 在紧急情况下“生产服务器着火”的情况下,我可能会使用查询或联接提示来立即解决性能问题,并在情况平静下来后再回去实施更好的解决方案。

谢谢阅读。 您可能还喜欢 在Twitter上关注我。

From: https://hackernoon.com/why-table-join-orders-in-relational-databases-dont-matter-6de3a35f2959

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值