MySQL JOIN 的评估顺序是啥?

Posted

技术标签:

【中文标题】MySQL JOIN 的评估顺序是啥?【英文标题】:In what order are MySQL JOINs evaluated?MySQL JOIN 的评估顺序是什么? 【发布时间】:2010-09-18 17:02:50 【问题描述】:

我有以下疑问:

SELECT c.*
FROM companies AS c
JOIN users AS u USING(companyid)
JOIN jobs AS j USING(userid)
JOIN useraccounts AS us USING(userid)
WHERE j.jobid = 123;

我有以下问题:

    USING 语法是否与 ON 语法同义? 这些连接是从左到右评估的吗?换句话说,这个查询是否说:x = Companies JOIN users; y = x 加入工作; z = y 加入用户帐户; 如果问题 2 的答案是肯定的,那么假设公司表具有 companyid、userid 和 jobid 列是否安全? 我不明白 WHERE 子句在引用别名“j”时如何用于选择公司表上的行

任何帮助将不胜感激!

【问题讨论】:

【参考方案1】:

这是关于JOIN 优先级的更详细答案。在您的情况下,JOINs 都是可交换的。让我们尝试一个他们没有的地方。

构建架构:

CREATE TABLE users (
  name text
);

CREATE TABLE orders (
  order_id text,
  user_name text
);

CREATE TABLE shipments (
  order_id text,
  fulfiller text
);

添加数据:

INSERT INTO users VALUES ('Bob'), ('Mary');

INSERT INTO orders VALUES ('order1', 'Bob');

INSERT INTO shipments VALUES ('order1', 'Fulfilling Mary');

运行查询:

SELECT *
  FROM users
       LEFT OUTER JOIN orders
       ON orders.user_name = users.name
       JOIN shipments
       ON shipments.order_id = orders.order_id

结果:

只返回 Bob 行

分析:

在此查询中,首先评估 LEFT OUTER JOIN,然后根据 LEFT OUTER JOIN 的复合结果评估 JOIN

第二次查询:

SELECT *
  FROM users
       LEFT OUTER JOIN (
         orders
         JOIN shipments
         ON shipments.order_id = orders.order_id)
         ON orders.user_name = users.name

结果:

Bob 的一行(包含履行数据)和 Mary 的一行,履行数据为 NULL。

分析:

括号改变了评估顺序。


更多 mysql 文档位于 https://dev.mysql.com/doc/refman/5.5/en/nested-join-optimization.html

【讨论】:

【参考方案2】:

在 MySQL 中,询问查询优化器它打算做什么通常很有趣:

EXPLAIN SELECT [...]

见"7.2.1 Optimizing Queries with EXPLAIN"

【讨论】:

【参考方案3】:

    USING (fieldname) 是 ON table1.fieldname = table2.fieldname 的简写方式。

    SQL 没有定义执行 JOINS 的“顺序”,因为这不是语言的本质。显然,必须在语句中指定顺序,但可以认为 INNER JOIN 是可交换的:您可以按任何顺序列出它们,并且会得到相同的结果。

    也就是说,在构造一个 SELECT ... JOIN 时,尤其是包含 LEFT JOIN 的那个,我发现将第三个 JOIN 视为将新表与第一个 JOIN、第四个 JOIN 的结果相连接是有意义的如加入第二次JOIN的结果,以此类推。

    更罕见的是,指定的顺序会影响查询优化器的行为,因为它会影响启发式算法。

    没有。查询的组装方式,它要求公司和用户都有一个 companyid,jobs 有一个 userid 和一个 jobid,useraccounts 有一个 userid。但是,只有一个公司用户需要用户 ID 才能使 JOIN 工作。

    WHERE 子句使用作业表提供的列过滤整个结果(即所有 JOIN 列)。

【讨论】:

“SQL 没有定义完成 JOINS 的‘顺序’”为假。请参阅“前面的示例演示了这些要点:”下的dev.mysql.com/doc/refman/5.5/en/nested-join-optimization.html: 我认为该链接无法说明您的观点。但是,我在将近 10 年前写了这个答案,所以我不记得我为什么这么说。但我可以推测这就是我理解 SQL 标准的含义。【参考方案4】:

1) Using 与 on 不完全相同,但它是简写,两个表都有一个与您要加入的名称相同的列...请参阅:http://www.java2s.com/Tutorial/MySQL/0100__Table-Join/ThekeywordUSINGcanbeusedasareplacementfortheONkeywordduringthetableJoins.htm

在我看来它更难阅读,所以我会拼出连接。

3) 从这个查询中不清楚,但我猜它没有。

2) 假设您通过其他表(并非所有直接在公司上)加入此查询中的顺序确实很重要...请参阅下面的比较:

原文:

SELECT c.* 
    FROM companies AS c 
    JOIN users AS u USING(companyid) 
    JOIN jobs AS j USING(userid) 
    JOIN useraccounts AS us USING(userid) 
WHERE j.jobid = 123

我认为它可能暗示的是:

SELECT c.* 
    FROM companies AS c 
    JOIN users AS u on u.companyid = c.companyid
    JOIN jobs AS j on j.userid = u.userid
    JOIN useraccounts AS us on us.userid = u.userid 
WHERE j.jobid = 123

您可以在此处切换加入工作和用户帐户的线路。

如果所有人都加入公司会是什么样子:

SELECT c.* 
    FROM companies AS c 
    JOIN users AS u on u.companyid = c.companyid
    JOIN jobs AS j on j.userid = c.userid
    JOIN useraccounts AS us on us.userid = c.userid
WHERE j.jobid = 123

这并没有真正的逻辑意义......除非每个用户都有自己的公司。

4.) sql 的神奇之处在于您只能显示某些列,但它们都是用于排序和过滤的...

如果你回来了

SELECT c.*, j.jobid....  

您可以清楚地看到它在过滤什么,但数据库服务器并不关心您是否输出一行进行过滤。

【讨论】:

【参考方案5】:

我无法回答有关 USING 语法的问题。这很奇怪。我以前从未见过,一直使用 ON 子句。

但我可以告诉你的是,JOIN 操作的顺序是由查询优化器在构建其查询计划时动态确定的,基于优化启发式系统,其中一些是:

    JOIN 是在主键字段上执行的吗?如果是这样,这将在查询计划中获得高优先级。

    JOIN 是在外键字段上执行的吗?这也得到了高度重视。

    连接字段上是否存在索引?如果是这样,请提高优先级。

    是否对 WHERE 子句中的字段执行 JOIN 操作?是否可以通过检查索引(而不是通过执行表扫描)来评估 WHERE 子句表达式?这是一个主要的优化机会,因此它获得了一个主要的优先级提升。

    连接列的基数是多少?具有高基数的列使优化器有更多机会区分错误匹配(不满足 WHERE 子句或 ON 子句的那些),因此通常在低基数连接之前处理高基数连接。

    李>

    联接表中有多少实际行?与仅包含 100 个值的表连接相比,连接具有一千万行的表所产生的数据爆炸更少。

无论如何...重点是...查询执行计划中有很多变量。如果您想了解 MySQL 如何优化其查询,请使用 EXPLAIN 语法。

这是一篇很好的文章:

http://www.informit.com/articles/article.aspx?p=377652


编辑中:

回答您的第 4 个问题:您没有查询“公司”表。您在 FROM 和 USING 子句中查询 ALL 四个表的连接叉积。

“j.jobid”别名只是该连接表集合中的一列的完全限定名称。

【讨论】:

真的是交叉产品吗?我认为 SELECT * FROM table_a JOIN table_b USING(common_column) 会产生 table_a 中与 table_b 的 common_column 字段的任何行匹配的所有行?这可能少于 n 行。叉积不会返回 n x m 多行吗? 对不起。我对 USING 语法一无所知,所以我无法评论它是如何工作的。 “叉积”注释只是指一般的连接,它能够创建元组的组合爆炸,这就是优化器考虑基数的原因。 我可能是错的,但我认为拥有索引总比没有索引要好。如果它对特定查询没有好处,优化器将忽略它。但是,索引可能设计不佳(字符串 idx 中的字符太多)并且它可能永远不会被使用,但我认为它永远不会有害。【参考方案6】:

我不确定 ON 与 USING 部分(尽管 website 表示它们是相同的)

至于排序问题,它完全是特定于实现(并且可能是查询)的。 MYSQL 最有可能在编译请求时选择一个顺序。如果您确实想强制执行特定顺序,则必须“嵌套”您的查询:

SELECT c.*
FROM companies AS c 
    JOIN (SELECT * FROM users AS u 
        JOIN (SELECT * FROM  jobs AS j USING(userid) 
              JOIN useraccounts AS us USING(userid) 
              WHERE j.jobid = 123)
    )

至于第 4 部分:where 子句限制了作业表中的哪些行有资格加入。因此,如果由于匹配的用户 ID 而存在要加入但没有正确作业 ID 的行,那么它们将被省略。

【讨论】:

【参考方案7】:

见http://dev.mysql.com/doc/refman/5.0/en/join.html

从这里开始阅读:


在 MySQL 5.0.12 中加入处理更改

从 MySQL 5.0.12 开始,自然连接和使用 USING 的连接,包括外连接变体,都根据 SQL:2003 标准进行处理。目标是根据 SQL:2003 将 MySQL 的语法和语义与 NATURAL JOIN 和 JOIN ... USING 保持一致。但是,连接处理中的这些更改可能会导致某些连接的输出列不同。此外,一些在旧版本中似乎可以正常工作的查询必须重写以符合标准。

这些变化主要有五个方面:

MySQL 确定 NATURAL 或 USING 连接操作的结果列(以及整个 FROM 子句的结果)的方式。

将 SELECT * 和 SELECT tbl_name.* 扩展为选定列的列表。

解析 NATURAL 或 USING 连接中的列名。

将 NATURAL 或 USING 连接转换为 JOIN ... ON。

在 JOIN ... ON 的 ON 条件下解析列名。

【讨论】:

以上是关于MySQL JOIN 的评估顺序是啥?的主要内容,如果未能解决你的问题,请参考以下文章

评估布尔语句的顺序是啥? [复制]

Java中评估顺序的规则是啥?

JOIN 子句中的 MySQL 逻辑评估是不是延迟/短路?

mysql - 如何强制更改内部连接的评估顺序?

Mysql 使用 JOIN 获取过去六周的数据

WEKA 中的集群评估是啥?