何时在 MySQL 中使用 STRAIGHT_JOIN

Posted

技术标签:

【中文标题】何时在 MySQL 中使用 STRAIGHT_JOIN【英文标题】:When to use STRAIGHT_JOIN with MySQL 【发布时间】:2010-10-05 11:11:47 【问题描述】:

我刚刚处理了一个相当复杂的查询,它需要 8 秒才能运行。 EXPLAIN 显示了一个奇怪的表顺序,即使使用 FORCE INDEX 提示,我的索引也没有全部使用。我遇到了 STRAIGHT_JOIN 连接关键字,并开始用它替换我的一些 INNER JOIN 关键字。我注意到显着的速度提高。最后,我只是用 STRAIGHT_JOIN 替换了我所有的 INNER JOIN 关键字,现在它在 0.01 秒内运行。

我的问题是你什么时候使用 STRAIGHT_JOIN 什么时候使用 INNER JOIN?如果您正在编写好的查询,是否有任何理由不使用 STRAIGHT_JOIN?

【问题讨论】:

为狮子头像点赞 【参考方案1】:

mysql 不一定擅长在复杂查询中选择连接顺序。通过将复杂查询指定为 straight_join,查询会按照指定的顺序执行连接。通过将表放在最不常见的位置,并指定 Straight_join,您可以提高查询性能。

【讨论】:

【参考方案2】:

来自MySQL JOIN reference:

“STRAIGHT_JOIN 与 JOIN 类似,只是左表总是在右表之前读取。这可用于连接优化器以错误顺序放置表的那些(少数)情况。”

【讨论】:

谢谢,但我已经阅读了 MySQL 手册。希望得到进一步的解释。【参考方案3】:

如果没有充分的理由,我不建议使用 STRAIGHT_JOIN。我自己的经验是 MySQL 查询优化器比我想要的更频繁地选择一个糟糕的查询计划,但不够频繁,你应该绕过它,如果你总是使用 STRAIGHT_JOIN,你会这样做。

我的建议是将所有查询保留为常规 JOIN。如果您发现一个查询正在使用次优查询计划,我建议您首先尝试重写或重新构建查询,看看优化器是否会选择更好的查询计划。此外,至少对于 innodb,请确保不仅仅是您的索引统计信息已过时(ANALYZE TABLE)。这可能会导致优化器选择一个糟糕的查询计划。优化器提示通常应该是您最后的手段。

不使用查询提示的另一个原因是您的数据分布可能会随着时间的推移而变化,或者您的索引选择性可能会随着表的增长而变化,等等。您现在最佳的查询提示可能会随着时间的推移变得次佳。但是由于您现在已经过时的提示,优化器将无法调整查询计划。如果您允许优化器做出决定,您将保持更灵活。

【讨论】:

这个答案实际上并没有解释何时使用 straight_join. 我认为如果有人对可预测的执行时间和STRAIGHT_JOINs 感到满意,他们应该使用它。【参考方案4】:

STRAIGHT_JOIN,使用该子句,可以控制JOIN的顺序:外循环扫描哪张表,内循环扫描哪一张。

【讨论】:

什么是外循环和内循环? @IstiaqueAhmed 表通过嵌套循环连接(从表 A 中取出第一行并循环抛出表 B,然后取出第二行......等等。这里表 A 位于外循环)跨度> 【参考方案5】:

这是最近在工作中出现的一个场景。

考虑三个表,A、B、C。

A 有 3,000 行; B 有 300,000,000 行; C 有 2000 行。

外键定义:B(a_id), B(c_id)。

假设您有一个如下所示的查询:

select a.id, c.id
from a
join b on b.a_id = a.id
join c on c.id = b.c_id

根据我的经验,在这种情况下,MySQL 可能会选择 C ​​-> B -> A。 C 比 A 小,B 很大,它们都是等值的。

问题在于 MySQL 不一定会考虑 (C.id 和 B.c_id) 与 (A.id 和 B.a_id) 之间的交集的大小。如果 B 和 C 之间的连接返回的行数与 B 一样多,那么这是一个非常糟糕的选择;如果从 A 开始将 B 过滤到与 A 一样多的行,那么这将是一个更好的选择。 straight_join 可用于强制执行此命令,如下所示:

select a.id, c.id
from a
straight_join b on b.a_id = a.id
join c on c.id = b.c_id

现在a 必须在b 之前加入。

通常,您希望按照使结果集中的行数最小化的顺序进行连接。因此,从一个小表开始并加入使得结果连接也很小,是理想的。如果从一张小桌子开始,然后将它连接到一张更大的桌子,结果就和大桌子一样大,事情就会变成梨形。

这取决于统计数据。如果数据分布发生变化,计算可能会发生变化。这也取决于join机制的实现细节。

我见过的 MySQL 最糟糕的情况是,除了需要straight_join 或激进的索引提示之外,所有查询都以严格的排序顺序对大量数据进行分页,并带有轻度过滤。 MySQL 强烈倾向于对任何过滤器和连接使用索引而不是排序;这是有道理的,因为大多数人不是试图对整个数据库进行排序,而是对查询做出响应的行的有限子集,并且对有限子集进行排序比过滤整个表要快得多,无论它是排序的还是不是。在这种情况下,将直接连接放在具有我想要对固定事物排序的索引列的表之后。

【讨论】:

你将如何使用直接连接来解决问题? @Hannele straight_join 先评估左表,再评估右表。因此,如果您想在我的示例中从 A -> B -> C 开始,则可以将第一个 join 关键字替换为 straight_join 啊,真好。将其作为示例包含在您的答案中会很有用:) 太棒了,感谢您提供惯用的示例数据集。 补充一点,根据我的经验,STRAIGHT_JOIN 并不总是简单地从左侧表连接到右侧,而是从左侧的任何表到给定表。【参考方案6】:
--use 120s, 18 million data
    explain SELECT DISTINCT d.taid
    FROM tvassist_recommend_list_everyday_diverse d, tvassist_taid_all t
    WHERE d.taid = t.taid
      AND t.client_version >= '21004007'
      AND t.utdid IS NOT NULL
      AND d.recommend_day = '20170403'
    LIMIT 0, 10000

--use 3.6s repalce by straight join
 explain SELECT DISTINCT d.taid
    FROM tvassist_recommend_list_everyday_diverse d
    STRAIGHT_JOIN 
      tvassist_taid_all t on d.taid = t.taid 
    WHERE 
     t.client_version >= '21004007'
       AND d.recommend_day = '20170403'

      AND t.utdid IS NOT NULL  
    LIMIT 0, 10000

【讨论】:

这并没有为您提供几乎足够的信息来确定何时适合直接连接。【参考方案7】:

我会告诉你为什么我必须使用 STRAIGHT_JOIN :

我在查询时遇到了性能问题。 简化查询,查询效率一下子提高了 试图找出导致问题的具体部分,但我无法确定。 (2 个左连接在一起很慢,每个都独立快) 然后我使用慢速和快速查询执行 EXPLAIN(添加左连接之一) 令人惊讶的是,MySQL 完全改变了 2 个查询之间的 JOIN 顺序。

因此,我强制其中一个连接为 straight_join 以强制先读取前一个连接。这阻止了 MySQL 更改执行顺序并像魅力一样工作!

【讨论】:

【参考方案8】:

在我短暂的经验中,STRAIGHT_JOIN 将我的查询从 30 秒减少到 100 毫秒的一种情况是执行计划中的第一个表不是按列排序的表

-- table sales (45000000) rows
-- table stores (3) rows
SELECT whatever
FROM 
    sales 
    INNER JOIN stores ON sales.storeId = stores.id
ORDER BY sales.date, sales.id 
LIMIT 50;
-- there is an index on (date, id)

如果优化器选择点击stores首先它将导致Using index; Using temporary; Using filesort,因为

如果 ORDER BY 或 GROUP BY 包含来自表以外的列 加入队列中的第一个表,创建一个临时表。

source

这里优化器需要一点帮助,告诉他首先使用sales

sales STRAIGHT_JOIN stores

【讨论】:

(我在你的回答上做了点缀。)【参考方案9】:

如果您的查询以ORDER BY... LIMIT... 结尾,则可能最好重新编写查询以诱使优化器执行LIMIT JOIN 之前。

(此答案不仅适用于有关STRAIGHT_JOIN 的原始问题,也不适用于STRAIGHT_JOIN 的所有情况。)

example by @Accountantم 开始,这在大多数情况下应该运行得更快。 (而且它避免了需要提示。)

SELECT  whatever
    FROM  ( SELECT id FROM sales
                ORDER BY  date, id
                LIMIT  50
          ) AS x
    JOIN  sales   ON sales.id = x.id
    JOIN  stores  ON sales.storeId = stores.id
    ORDER BY  sales.date, sales.id;

注意事项:

首先,获取 50 个 ID。使用INDEX(date, id) 会特别快。 然后返回到 sales 的连接让您只获得 50 个“whatevers”而无需将它们拖到临时表中。 因为根据定义,子查询是无序的,ORDER BY 必须在外部查询中重复。 (优化器可能会找到一种方法来避免实际执行其他排序。) 是的,它更乱。但它通常更快。

我反对使用点击,因为“即使今天更快,明天也可能无法更快。”

【讨论】:

【参考方案10】:

我知道它有点旧,但这是一个场景,我一直在做批处理脚本来填充某个表。在某些时候,查询运行得很慢。特定记录上的连接顺序似乎不正确:

按正确顺序

将 id 增加 1 会打乱顺序。注意“额外”字段

使用 straight_join 可以解决问题

不正确的顺序运行大约 65 秒,而使用 straight_join 以毫秒为单位运行

【讨论】:

以上是关于何时在 MySQL 中使用 STRAIGHT_JOIN的主要内容,如果未能解决你的问题,请参考以下文章

MySQL索引的分类何时使用何时不使用何时失效?

何时在 mysql 中使用 Union [关闭]

MySQL 配置:何时使用连字符以及何时使用下划线

PHP和MySQL中何时使用'@'运算符[重复]

何时在 mysql 中使用 TEXT 而不是 VARCHAR [重复]

何时在 PHP 应用程序中使用 Redis 而不是 MySQL?