共计 1419 个字符,预计需要花费 4 分钟才能阅读完成。
问题
在 26 问中,咱们看到了如下 SQL 在 MySQL 5.7 中跑得很慢:
咱们还剖析了执行打算改写后的 SQL,通过猜想,减少了 hint 来解决问题:
这一期,咱们通过工具来剖析一下:MySQL 为什么会应用一个低效的执行打算,以致于咱们不得已用 hint 来调优 SQL?
试验
咱们接着应用 26 问中的环境,应用 optimizer trace 工具,察看 MySQL 对 SQL 的优化处理过程。
咱们先调大 optimizer trace 的内存容量(否则 trace 的输入会被截断),而后开启了 optimizer trace 性能。
跑完 SQL 后,能够在 INFORMATION_SCHEMA.OPTIMIZER_TRACE 看到 SQL 的优化处理过程:
这会是个微小的 json,咱们将其复制进去,找个 json 的可视化编辑器来剖析一下。
小贴士
如果 MySQL 启动时有配置 –secure-file-priv,那能够用,
SELECT TRACE INTO DUMPFILE <filename> FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
将 trace 导出到文件里,会更不便一些。
这里咱们抉择了一个在线的 json 编辑器,应用起来会不便一点:
能够看到整个优化过程分为 6 个步骤,前两步都跟创立长期表相干,而后是 join 的筹备工作,再是两步 join 优化,最初是 join 的执行。
回顾一下 26 问中,咱们的子查问应应用物化形式,但理论应用了 exists 子句形式,咱们猜想这个抉择是在 join 的优化阶段做出的。
认真翻一翻,就会找到可疑的局部:
上图中的中文,是从英文翻译过去的。看上去咱们找对了地位。
接下来咱们逐渐看看这个决策的根据是什么:
显然不物化的代价更小,那么优化器抉择不物化是正确的抉择。
但应用 exists 子句进行子查问的代价,显然不可能为 0,MySQL 对这个代价的计算可能有误。
咱们得来看看 MySQL 是如何计算这个代价的:
执行 exists 子查问的代价 = 执行一次子查问的代价 * 子查问须要执行的次数
显然这个子查问不可能只须要执行 0 次
这里须要做一个额定的思考:在这个场景下,子查问须要执行的次数,与父查问的行数雷同。
也就是红框内须要执行的次数,取决于红框外的 SQL 的后果集条数。
这里 MySQL 将父表的后果集条数 称为 “ 扇出度 ”(fanout)
显然,这里父表 information_schema.columns 的扇出度为 0,间接导致了优化器放弃了物化的策略
那 information_schema.columns 的扇出度为什么是 0 呢?
查看 information_schema.tables 中对于 COLUMNS 表的形容,咱们看到 MySQL 将 information_schema 中的元数据表做了非凡看待,其行数预计是没有意义的。
到此咱们找到了问题所在:MySQL 5.7 对元数据表应用了区别设计,与一般表的行数估算形式不同。
当前大家在 MySQL 5.7 中应用 information_schema 中的元数据表做简单查问时,须要额定留神执行打算,可能须要应用 hint 领导优化器工作。
小贴士
MySQL 8.0 中进行了数据字典的革新,information_schema 中的元数据表大部分都变成了视图,其实在的数据源是 mysql 库中的暗藏元数据表。
对 MySQL 8.0 的元数据表进行简单查问,执行打算会比 MySQL 5.7 更加正当。
对于 MySQL 的技术内容,你们还有什么想晓得的吗?连忙留言通知小编吧!