摘要:
最近在搞一个列存储引擎的包含内连接的嵌套外连接过慢的问题, 连接执行过慢的原因分析见此前的博客分析, 虽然逻辑很绕, 但是也不是无法分析.
更麻烦的问题在于修改查询计划, 让其能按照代价更小的方式正确的执行.
遇到的问题比我在修改查询计划前设想的更为棘手, 本文做下记录.
包含内连接的嵌套外连接执行过慢的问题原因分析:
- 最关键的问题出在内连接生成的中间结果的大小上
- 在mysql/sql层, 将内连接的条件全部上推, 导致内连接没有过滤条件,结果就是内连接的结果是R与S的叉乘
- 外连接U与(R和S的叉乘)做运算, 现在条件全部集中在外连接这一层, 连接的时间复杂度达到了惊人的 U 连接 (R叉乘S)
- 外连接的执行策略为散列连接, 对U做构建散列, 使用R叉乘S的结果集做探测集
- 虽然在逻辑理解上可以认为是对U做构建散列,但是这里的散列是一种连续地址的空间, 每次查找元素, 时间复杂度并不是槽位映射的O(1), 而是要从头遍历整个连续地址空间的O(n)
- 所以整个的包含内连接的嵌套外连接的执行时间复杂度可以这么理解
- 对外表U构建散列, 耗时为 M(U)
- 使用R叉乘S的结果集做探测集, 设散列探测单个元素的时间复杂度为O(1), 这里先忽略线性地址空间的O(n)的复杂度导致的问题复杂性的升级, 那么探测过程的耗时可以理解为 N(R*S)
- 总的耗时可以理解为 M(U) + N(R*S)
- 在执行的过程中, 最大的问题便是R*S的结果集过大, 注意这里是完整的R和S的叉乘, 中间结果并没有经过条件的过滤
- 那么解决问题的思路, 便是顺着减少R与S的叉乘的结果集入手, 这里有两种不同的思路
- 停止将内连接的条件上推, 在执行R与S的内连接时, 对中间结果进行过滤
- 基于外连接与内连接的集合论与包的理论, 将U外连接(R内连接S), 做集合的分解, 处理成 U连接R + U 连接S
优化R叉乘S的中间结构的思路分析:
一. 结合集合论与包的理论, 分解U外连接(R内连接S)
- 这种做法在理论上具有直观性
- 所涉及的逻辑计划和物理计划所要修改的地方过多, 无法保证解决掉每个场景上可能出现的问题
- 在可以预料到的时间内无法做到bug zero
二. 不对内连接的条件上推, 对内连接的结果执行过滤并直接物化
- 在设计层面所要做的修改更少, 注意只是理论上的, 实际要要做的工作取决于对当前列存储引擎的理解的程度
- 对查询序列做的修改的过程中, 保持了直观性, 而避免了相对而言过多的抽象性, 也就是每一步的处理都保持了所见即所得, 这样有助于定位过程中出现的问题, 其实本质上还是因为对当前代码的驾驭力不足以做更为剧烈的改动
- 综上, 本质还是对连接处理的驾驭力的问题
- 而所谓驾驭力的体现上, 就是对当前列存储引擎对于将mysql/sql层的查询树的结构, 转换为自己的查询序列的结构并执行的过程,这中间所涉及的细节的驾驭的问题
对内连接不做条件上推并直接物化所遇到的问题:
虽然相对来说, 对内连接做直接物化减少中间结果集的大小, 所遇到的问题更少, 但是在具体处理的时候, 还是遇到了不少问题:
- 修改逻辑计划中包含内连接与外连接的表之间的关系倒是简单,因为虽说这种关系是一种递归, 但是毕竟非常直接, 不存在更多的理论上的盲区
- 直接修改mysql/sql层的内连接条件上推的处理后, 列存储引擎对于该查询树的处理存在问题, 无法正确的识别, 表现在构建cond时存在表关系依赖的失败
- 当修改完列存储的逻辑执行序列后, 开始遇到一系列关于表之间关系, 条件与表之间关系的问题
- 列存储引擎在逻辑执行序列的预处理阶段, 发生外连接的位图的识别的错误
- 这里值得注意, 列存储引擎的查询执行序列, 使用了大量对于表和元素的重构的处理, 类似于monetdb中的BAT的执行序列, 但是monetdb的BAT执行更为严格, 每一步仅仅为最原始的执行步骤, 而列存储引擎的查询执行序列, 虽然执行的单元也是以列为中心, 但是却是以关系为出发点, 条件之间的组合与表之间的关系更为自由
- 在执行条件过滤的时候, 会对查询执行序列做动态的修改, 可以理解成将一部分的物理优化延迟到了具体执行该查询序列之前, 包括:
- 根据空值拒绝将外连接转换为内连接
- 对一些可直接拿到结果的连接的集合进行直接物化
- 这个过程中会对表之间的关系, 以及条件与表的关系进行处理, 当对包含内连接的嵌套外连接的查询执行序列做修改后, 相关的关系被破坏, 根本原因还是对列存储引擎的执行的细节理解的不够深刻
- 在散列连接的散列构建阶段, 会对外连接与内连接的表之间的关系做处理
- 可以理解为散列连接的预处理阶段, 对小表进行散列构建
- 这个过程会继续使用表之间的位图关系, 进行表间关系与条件关系的检测处理
- 此过程发生的最明显的问题便是存在外连接或内连接的表位图占位的丢失导致检测失败
一些分析草图:
参考:
MySQL :: MySQL 5.7 Reference Manual :: 8.2.1.7 Nested Join Optimization