有人能解释一下为什么在 mysql 中左加入两个视图这么慢吗?

LOS*_*nDB 7 mysql join view

这是我昨天问的一个问题 - /sf/ask/1552650921/

我得到了一个很好的答案,对我有帮助,但我不明白为什么 LEFT JOIN 比查找慢得多。LEFT JOIN 是 16 秒 - 我很确定我的表至少优化了 90% - 在进行查找时它只有 0.14 秒。当我 LEFT JOIN 表时,它并没有那么慢,为什么要查看?

Rol*_*DBA 10

根据关于视图MySQL 文档

视图(包括可更新的视图)在 MySQL Server 5.6 中可用。视图是存储查询,调用时会生成结果集。视图充当虚拟表。

关于视图,必须意识到的第一件事是它产生一个结果集。从视图调用的查询中产生的结果集是一个虚拟表,因为它是按需创建的。没有您可以事后调用以立即索引结果集的 DDL。出于所有意图和目的,结果集是一个没有任何索引的表。实际上,您正在执行的 LEFT JOIN 基本上是带有一些过滤的笛卡尔积。

为了让您更详细地了解两个视图的 JOIN,我将参考我去年发表的一篇文章,其中解释了 MySQL 用于评估 JOIN 和 WHERE 的内部机制(JOIN 条件和 WHERE 条件之间是否存在执行差异?)。我将向您展示在《了解 MySQL 内部》(第 172 页)中发布的机制:

  • 确定哪些键可用于从表中检索记录,并为每个表选择最好的一个。
  • 对于每个表,决定表扫描是否比读取键更好。如果匹配key值的记录很多,key的优势就减少了,表扫描变快了。
  • 当查询中存在多个表时,确定应连接表的顺序。
  • 重写 WHERE 子句以消除死代码,减少不必要的计算并尽可能更改约束以打开使用密钥的方式。
  • 从连接中删除未使用的表。
  • 确定键是否可用于ORDER BYGROUP BY
  • 尝试简化子查询,并确定可以将其结果缓存到什么程度。
  • 合并视图(将视图引用扩展为宏)

好的,似乎应该使用索引。然而,仔细看。如果你用 wordView代替Table,看看机制的执行会发生什么:

机制修改

  • 确定哪些键可用于从中检索记录views,并为每个键选择最好的一个view
  • 对于每个view,决定view扫描是否比读取键更好。如果匹配key值的记录很多,key的优势就减少了,view扫描变快了。
  • viewsviews查询中存在多个时,确定应加入的顺序。
  • 重写 WHERE 子句以消除死代码,减少不必要的计算并尽可能更改约束以打开使用密钥的方式。
  • views从连接中消除未使用。
  • 确定键是否可用于ORDER BYGROUP BY
  • 尝试简化子查询,并确定可以将其结果缓存到什么程度。
  • 合并视图(将视图引用扩展为宏)

每个表(视图)都没有索引。因此,在执行 JOIN 时,使用虚拟表、临时表或没有索引的表确实变得模糊不清。使用的键仅用于 JOIN 操作,而不是用于更快地查找。

您的查询想象为拿起两本电话簿,即 2014 年黄页和 2013 年黄页。每本黄页手册都包含住宅电话号码的白页。

  • 2012 年底,使用数据库表生成 2013 年黄页。
  • 2013年期间
    • 人们更改了电话号码
    • 人们收到了新的电话号码
    • 人们丢掉电话号码,改用手机
  • 2013 年底,使用数据库表生成 2014 年黄页。

显然,两个电话簿之间存在差异。对数据库表进行 JOIN 以找出 2013 和 2014 之间的差异应该没有问题。

想象一下手动合并两个电话簿以定位差异。听起来很疯狂,不是吗?尽管如此,这正是您在加入两个视图时要求 mysqld 执行的操作。请记住,您并没有加入真正的表,也没有可以搭载的索引。

现在,让我们回顾一下实际的查询。

SELECT DISTINCT
viewA.TRID, 
viewA.hits,
viewA.department,
viewA.admin,
viewA.publisher,
viewA.employee,
viewA.logincount,
viewA.registrationdate,
viewA.firstlogin,
viewA.lastlogin,
viewA.`month`,
viewA.`year`,
viewA.businesscategory,
viewA.mail,
viewA.givenname,
viewA.sn,
viewA.departmentnumber,
viewA.sa_title,
viewA.title,
viewA.supemail,
viewA.regionname
FROM
viewA
LEFT JOIN viewB ON viewA.TRID = viewB.TRID
WHERE viewB.TRID IS NULL 
Run Code Online (Sandbox Code Playgroud)

您正在使用一个虚拟表(没有索引的表)viewA,将它连接到另一个虚拟表 viewB。间歇性生成的临时表将与 viewA 一样大。然后,您在大型临时表上运行内部排序以使其不同。

结语

鉴于评估 JOIN 的内部机制,以及视图结果集的瞬态和无索引性质,您的原始查询(两个视图的 LEFT JOIN)应该获得数量级的运行时间。同时,鉴于我刚刚描述的相同 JOIN 算法,您从 StackOverflow 得到答案应该表现良好。

我希望我刚刚发布的血腥细节能回答您关于原因的问题。