Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MySQL优化器汇总

qs:

  • mysql 的某些操作会内部进行一些特殊的处理,不知道是基于什么原因实现的,但是不是太符合规范, 例如
    • 表达式运算中,会隐式转换,且转换之后不报错,转换规则也不是很符合常理,例如字符转数值,字符串转日期等,不清楚规则的情况下,隐式转换会导致出现预期之外的结果,或者index失效等
    • 一些函数行为不符合预期,例如 || 和 concat,round 函数等
  • MySQL有没有某个函数每次输出不一样,且每次调用之后会导致某个状态发生改变
    • 使用函数或者sp,实现一个类似的函数
    • DETERMINISTIC 属性
  • MySQL 相同的函数名,接受不同的参数类型,执行不同的逻辑
    • abs, greatest, least 等函数支持不同参数或者不同类型,需要继承 Item_func_numhybrid

MySQL 的表示使用继承实现,但是在 SQL 中,表达式的运算是很频繁的,使用继承实现会不会导致虚指针而导致性能问题 pg 为了加速表达式运算,已经重构过一次表达式,MySQL 相比于 pg,TPCH 的 q1 在所有的测试中,是性能比较弱的

  • 减少函数调用,使用 goto 替换 switch,减少分支预测的开销,减少运行时的操作,更多的操作在 init 的时候实现 TODO: perf 采集下 tpch q1 MySQL 和 pg 的差距
  • 表达式的运算逻辑,以 tpch q1 为例

tpch q20

dispatch_command dispatch_sql_command reset_for_next_command lex_start parse_sql { // 解析SQL语句,生成 PT_xx AST 树,之后生成 Sql_cmd add_table_to_list add_joined_table } mysql_rewrite_query // 有配置重写就尝试重写 mysql_execute_command { // 主要的执行逻辑,包括 opt 和 execute Sql_cmd_dml::execute { Sql_cmd_dml::prepare { check_table_access // 检查表权限
open_tables_for_query // 打开表 Query_block::prepare { // opt 前的准备工作,主要类似 pg 的analyze,解析类型,解析表字段,除此之外还有部分优化操作 setup_tables setup_fields setup_conds setup_order setup_connect_by resolve_limits resolve_subquery } } lock_tables { mysql_lock_tables decide_logging_format } Query_expression::optimize { Query_block::optimize { JOIN::optimize{ .. preprocess optimize_cond // 表达式优化, eg. 常量折叠,条件合并,等值传递,死条件删除等 JOIN::make_join_plan { init_planner_arrays update_ref_and_keys pull_out_semijoin_tables estimate_rowcount // 计算依据是什么,会根据条件计算吗 } } } } } }

Here is an overview of the logic of this function:

- Initialize JOIN data structures and setup basic dependencies between tables.

- Update dependencies based on join information. 对于存在outer join或recursive的tables进行关系传递propagate_dependencies()(用传递闭包算法),构建出完整的依赖关系。(recursive这里具体指代未确定,nested?WITH RECURSIVE语法?)

- Make key descriptions (update_ref_and_keys()). 这一步骤较为烦杂,本意是想从conditions中找出join连接的condition,并识别出join condition相关的key(key指的就是索引),为后续决定join_type到底是ref/ref_or_null/index等做好准备。但MySQL在这一步又加了不少特殊判断,比如对key is null的特殊处理等。

- Pull out semi-join tables based on table dependencies.

- Extract tables with zero or one row as const tables. 从这步开始的四个步骤都是const table优化,核心就是先把const table算出来,将变量替换成常量。这里是依靠获取采样判断const table。

- Read contents of const tables, substitute columns from these tables with
  actual data. Also keep track of empty tables vs. one-row tables.

- After const table extraction based on row count, more tables may
  have become functionally dependent. Extract these as const tables.

- Add new sargable predicates based on retrieved const values.

- Calculate number of rows to be retrieved from each table. 获取采样结果的步骤。

- Calculate cost of potential semi-join materializations.

- Calculate best possible join order based on available statistics. 即下文的Optimize_table_order::choose_table_order

- Fill in remaining information for the generated join order.

qs

  • 表达式

    • 类型怎么绑定的
    • 结构怎么组织的
    • 常用的优化规则有那些,什么阶段生效
    • 表达式和子查询的联系及处理方法
    • 在执行阶段怎么evaluate的
    • 对比其他数据库的实现,当前实现有什么问题,怎么优化 ** 实现一个简单表达式
  • 子查询

    • yacc怎么生成子查询
    • 子查询类型
    • 子查询怎么消除,具体的消除规则
    • 无法消除的子查询怎么处理,在优化阶段以及运行阶段
    • 子查询的代价计算 ** 对比其他数据库,存在什么问题,怎么优化
  • join

    • join order 怎么确定的
    • join type 有哪些,怎么确定的
    • 怎么从下层 path 构造 join
    • cost 怎么影响 join 的选择
    • nest loop join 的优化
    • 运行时怎么执行 join ** 实现merge join有什么难点
  • scan

    • 有哪些类型,包括 index 和 其他scan
    • 索引的选择和使用
    • 索引的维护和更新
    • 条件怎么处理的
    • 代价怎么计算的
  • 聚合操作是怎么优化的

  • sort 是怎么处理的

  • 基于规则的优化和基于代价的优化,分别做了什么

  • 参数化路径,join 中 a=b 这种,估算左右path的时候,怎么计算

  • 对于一个组件,需要评估其使用情况,是否有必要使用,或者需要达到什么效果

    • 例如对于 explain,table 格式还需要存在吗
  • Query_block::prepare 应该只关注于语言分析,不应该参杂太多优化工作

  • parse:

    • 相比于 pg,差的不是一点半点,pg 的 parse 低耦合,已经可以作为一个单独的模块,被广泛使用,大多数新兴的基于使用c或者c++的数据库,不想写 parse 的,大多数可以直接使用 pgparse
      • lex 词不达意,一般是指词法分析,这里更类似parse ctx
    • 使用 show PARSE_TREE 显示语法树
  1. 通过 contextualize,遍历 tree,构造以 query_block 为单位的语法树,同时在遍历过程中,检测某些关键信息,设置一些 flag
  • 这里比其他数据库多了一层 contextualize,contextualize 还只是简单的遍历然后进行一个转换的过程,还没有使用具体的元数据 ? 为什么要 contextualize? parse 里面不好构建 query_expression 和 query_block 树,所以需要 contextualize 层来补全语法树,然后再进行优化器的处理。
  1. resolve:设置字段类型,检测表达式以及语句的合法性
  • 类似 pg 的analyze,这里依赖与前面处理之后的 query_block 语法树,进行类型检查,表达式检查等

  • 这里还包含语句的改写功能,例如子查询消除,outer join 转换等 ? 为什么不把 contextualize 和 resolve 合在一起处理,而且为什么把一些优化操作放在这里,统一由 optimizer 管理不行吗

  • optimizer 处理的不是一个树形结构,而是 Query_expression 和 Query_block 组合的逻辑树

    • 怎么理解 Query_expression 和 Query_block 组合的逻辑树?
      • lex->unit 指向的是当前整个完整的 SQL 语句的 Query_expression
      • Query_block
        • master 指向的是包含当前的 Query_block 的 Query_expression
        • slave 指向的是当前 Query_block 的子查询的 Query_expression,可能是 union,也可能是子查询
        • next 指向的是同属于同一个 Query_expression 的 block 链表
        • link_next,link_prev 是维护所有当前语句中的 Query_block 的双向链表
      • Query_expression
        • m_query_term 当前 expression 的主要内容,可能是一个 union,或者就是一个 Query_block
        • 这个结构体只是为了维护同属于一个查询的 Query_block 的关系,暂时不了解其他作用,完全可以由 block 自己维护
        • slave 指向当前所属的第一个 Query_block
        • master 指向包含当前 Query_expression 的 Query_block