何时在MySQL上使用STRAIGHT_JOIN


88

我刚刚遇到了一个非常复杂的查询,要花8秒钟才能运行。EXPLAIN显示了一个奇怪的表顺序,即使使用FORCE INDEX提示也没有全部使用我的索引。我遇到了STRAIGHT_JOIN连接关键字,并开始用它替换一些INNER JOIN关键字。我注意到速度有了很大提高。最终,我为此查询将所有INNER JOIN关键字替换为STRAIGHT_JOIN,现在运行时间为.01秒。

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

Answers:


73

我不建议在没有充分理由的情况下使用STRAIGHT_JOIN。我自己的经验是,MySQL查询优化器选择不良查询计划的频率比我想要的要多,但不足以使您通常绕开它,如果您始终使用STRAIGHT_JOIN,这就是您要做的。

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

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


59
这个答案实际上并没有解释什么时候使用 straight_join
Pacerier,2015年

23

MySQL JOIN参考

“ STRAIGHT_JOIN与JOIN相似,不同之处在于,总是在右表之前读取左表。这可以用于(很少)联接优化器将表以错误顺序放置的情况。”


26
谢谢,但是我已经阅读了MySQL手册。希望有进一步的解释。
格雷格,

20

这是最近一次在工作中出现的情况。

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

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

定义了外键: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

通常,您希望以最小化结果集中的行数的顺序进行连接。因此,从一个小表开始并进行连接以使生成的连接也较小,这是理想的选择。如果从一个小桌子开始,然后将其连接到一个更大的桌子上,那么事情就变成了梨形,而结果却和大桌子一样大。

虽然这取决于统计数据。如果数据分布发生变化,则计算可能会发生变化。它还取决于联接机制的实现细节。

对于MySQL,我看到的最糟糕的情况是,除了必需的straight_join索引索引或主动索引索引索引外,其余所有查询都是按严格的排序顺序使用光过滤对大量数据进行分页的查询。MySQL强烈希望对任何过滤器使用索引,并且对排序进行联接。这是有道理的,因为大多数人不是在尝试对整个数据库进行排序,而是拥有有限的行子集来响应查询,并且对有限的子集进行排序比筛选整个表要快得多,无论是对表进行排序还是对表进行过滤不。在这种情况下,将直接连接放在要对固定内容进行排序的具有索引列的表之后。


您将如何使用直接联接解决问题?
Hannele

@Hannele先straight_join评估左表,再评估右表。因此,如果您想从A -> B -> C我的示例中删除,第一个join关键字可以替换为straight_join
巴里·凯利

干净利落 将其作为示例包含在您的答案中将是有用的:)
Hannele '18

18

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


11

STRAIGHT_JOIN使用此子句,您可以控制JOIN顺序:在外部循环中扫描哪个表,在内部循环中扫描哪个表。


什么是外循环和内循环?
Istiaque Ahmed

@IstiaqueAhmed表由嵌套循环连接(从表A中获取第一行,并向表B中抛出循环,然后获取第二行...依此类推。这里的表A位于外部循环中)
会计师م18年

6

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

  • 我在查询中遇到性能问题。
  • 简化查询,查询效率突然提高
  • 试图弄清是哪个具体部分导致了问题,但我做不到。(2个左连接在一起速度很慢,而每个独立连接速度很快)
  • 然后,我用慢速查询和快速查询执行了EXPLAIN(添加左联接之一)
  • 令人惊讶的是,MySQL完全更改了两个查询之间的JOIN顺序。

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


2

以我的短暂经验,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包含联接队列中第一个表以外的表中的列,则会创建一个临时表。

资源

在这里,优化程序需要一些帮助,方法是告诉他sales首先使用

sales STRAIGHT_JOIN stores

1
(我对您的回答很满意。)
瑞克·詹姆斯

2

如果你的查询结束ORDER BY... LIMIT...,它可能是最佳的重新制定查询优化器诱骗做LIMIT 之前JOIN

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

@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个“无论如何”,而无需将它们拖到临时表中。
  • 根据定义,由于子查询是无序的,因此ORDER BY必须在外部查询中重复该子查询。(优化程序可能找到一种避免实际进行其他排序的方法。)
  • 是的,它更混乱。但这通常更快。

我反对使用热门歌曲,因为“即使今天更快,明天也可能会更快”。


0

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

  • 顺序正确

在此处输入图片说明

  • 将id递增1会弄乱顺序。注意“额外”字段

在此处输入图片说明

  • 使用Straight_join解决了该问题

在此处输入图片说明

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


-5
--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

3
这不能为您提供足够的信息来确定什么时候适合使用直联接。
Hannele '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.