前言

Explain Format Tree依赖的是mysql新引入的iterator tree格式的执行计划。通过递归遍历iterator tree来完成Format Tree格式的执行计划的展示。 Iterator的组织形式的树形的,通常的Iterator都只有一个children(JOIN iterator有两个,分别是JOIN的外表和内表),所有在递归遍历过程中直接访问的Iterator都是RowIterator的子类。

下面是一个Explain Format Tree的例子和解读:

上面是一个典型的Mysql查询,包含了join, group by, order by 和 limit 通过上面的计划我们可以知道整个SQL的执行过程是

  1. 对t2表进行scan和过滤,t2是驱动表
  2. 将结果和t1表进行nest loop join,join条件是 t1.a1 = t2.b2
  3. 对join的结果进行聚集计算,计算的通过临时表进行的,聚集结果也保存在临时表中
  4. 对临时表的内容进行排序
  5. 对排序结果limit 10输出

这个例子的Iterator Tree的样子是:

  1. |-> SortingIterator
  2. |-> TableScanIterator
  3. |-> TemptableAggregateIterator
  4. |-> NestLoopIterator
  5. |-> FilterIterator
  6. | |-> TableScanIterator
  7. |-> FilterIterator
  8. |-> TableScanIterator

RowIterator是所有Iterator的基类,下面列出了explain相关的主要成员和方法:

  1. class RowIterator {
  2. public:
  3. struct Child {
  4. RowIterator *iterator;
  5. std::string description;
  6. };
  7. virtual std::vector<Child> children() const { return std::vector<Child>(); } // 返回iterator的children list,在递归中使用
  8. virtual std::vector<std::string> DebugString() = 0 // 输出Iterator的计划信息,不同的Iterator会有各自的实现
  9. JOIN *join_for_explain() const { return m_join_for_explain; } // 如果是join的root_iterator会返回join的指针,用来遍历join上的子查询并输出子查询的查询计划。
  10. private
  11. double m_estimated_cost = -1.0; // Iterator执行完成预期的代价
  12. double m_expected_rows = -1.0; // Iterator输出的行数
  13. };

RowIterator::children

返回当前iterator的children list,包装结构为Child,部分Iterator在实现该方法时除了返回childrend iterator的指针以外,还返回了description。 例如:

Iterator tree的结构如下:

  1. HashJoinIterator
  2. --> TableScanIterator // t2, probe side
  3. --> TableScanIterator // t1, build side

HashJoinIterator的children方法返回的内容如下:

  1. {<TableScanIterator(t2), null>,
  2. <TableScanIterator(t1), "Hash">}

产生Iterator的格式化字符串, 每个Iterator都有自己的实现,主要是描述Iterator的行为,包括Table Scan的方式, 过滤的Condition,Join method等等, 通常都是一行。

Explain Format Tree 介绍

执行过程

Explain

  1. std::string PrintQueryPlan(int level, RowIterator *iterator) {
  2. string ret;
  3. if (iterator == nullptr) {
  4. ret.assign(level * 4, ' ');
  5. return ret + "<not executable by iterator executor>\n";
  6. int top_level = level; // 当前缩进level, 每个level缩进4个空格
  7. // 输出自身的计划信息
  8. for (const string &str : FullDebugString(current_thd, *iterator)) {
  9. ret.append(level * 4, ' ');
  10. ret += "-> ";
  11. ret += str;
  12. ret += "\n";
  13. ++level;
  14. // 遍历所有的children iterator, 递归输出计划信息
  15. for (const RowIterator::Child &child : iterator->children()) {
  16. if (!child.description.empty()) {
  17. ret.append(level * 4, ' ');
  18. ret.append("-> ");
  19. ret.append(child.description);
  20. ret.append("\n");
  21. ret += PrintQueryPlan(level + 1, child.iterator);
  22. } else {
  23. ret += PrintQueryPlan(level, child.iterator);
  24. }
  25. }
  26. //
  27. if (iterator->join_for_explain() != nullptr) {
  28. for (const auto &child :
  29. GetIteratorsFromSelectList(iterator->join_for_explain())) {
  30. ret.append(top_level * 4, ' ');
  31. ret.append("-> ");
  32. ret.append(child.description);
  33. ret.append("\n");
  34. ret += PrintQueryPlan(top_level + 1, child.iterator);
  35. }
  36. }
  37. return ret;
  38. }

FullDebugString()

通过本方法输出一个iterator的完整的计划信息,包括iterator本身的信息,cost信息和执行信息

  1. vector<string> FullDebugString(const THD *thd, const RowIterator &iterator) {
  2. vector<string> ret = iterator.DebugString(); // 生成iterator的计划信息
  3. if (iterator.expected_rows() >= 0.0) { // 生成cost info
  4. // NOTE: We cannot use %f, since MSVC and GCC round 0.5 in different
  5. // directions, so tests would not be reproducible between platforms.
  6. // Format/round using my_gcvt() and llrint() instead.
  7. char cost_as_string[FLOATING_POINT_BUFFER];
  8. my_fcvt(iterator.estimated_cost(), 2, cost_as_string, /*error=*/nullptr);
  9. char str[512];
  10. snprintf(str, sizeof(str), " (cost=%s rows=%lld)", cost_as_string,
  11. llrint(iterator.expected_rows()));
  12. ret.back() += str;
  13. }
  14. if (iterator.expected_rows() < 0.0) {
  15. // We always want a double space between the iterator name and the costs.
  16. ret.back().push_back(' ');
  17. ret.back().push_back(' ');
  18. ret.back() += iterator.TimingString();
  19. }
  20. return ret;
  21. }

PolarDB的Parallel Query功能引入了新的exchange算子, 同时多阶段并行计划在生成计划的过程中是基于Cost选择的最优计划,我们对Parallel Query计划Format Tree的输出信息进行了完善和补充, 下面是Parallel Query计划的Explain Format Tree的一个简单例子。

  1. 对 group by/ distinct / order by / window / limit iterator增加了代价显示的支持
  2. 新增了exchange算子和exchange算子详细信息的显示,包括算子类型(gather/repartition/nroadcast), slice id, parallel worker number。 对于repartition的exchange算子,同时还显示了repartition column。

附录

  1. RowIterator <|--- TimingIterator
  2. |- UnqualifiedCountIterator
  3. |- FakeSingleRowIterator
  4. |- ZeroRowsIterator
  5. |- ZeroRowsAggregatedIterator
  6. |- FollowTailIterator
  7. |- FilterIterator
  8. |- LimitOffsetIterator
  9. |- AggregateIterator
  10. |- PrecomputedAggregateIterator
  11. |- NestedLoopIterator
  12. |- CacheInvalidatorIterator
  13. |- WeedoutIterator
  14. |- RemoveDuplicatesIterator
  15. |- NestedLoopSemiJoinWithDuplicateRemovalIterator
  16. |- WindowingIterator
  17. |- BufferingWindowingIterator
  18. |- MaterializeInformationSchemaTableIterator
  19. |- AppendIterator
  20. |- HashJoinIterator
  21. |- SortingIterator
  22. |- TableRowIterator <|--- TableScanIterator
  23. |- IndexScanIterator
  24. |- IndexRangeScanIterator
  25. |- SortBufferIterator
  26. |- SortBufferIndirectIterator
  27. |- SortFileIterator
  28. |- SortFileIndirectIterator
  29. |- MaterializeIterator
  30. |- StreamingIterator
  31. |- TemptableAggregateIterator
  32. |- MaterializedTableFunctionIterator
  33. |- RefIterator
  34. |- RefOrNullIterator
  35. |- EQRefIterator
  36. |- ConstIterator
  37. |- FullTextSearchIterator
  38. |- DynamicRangeIterator
  39. |- PushedJoinRefIterator

注:文中引用代码是基于mysql-8.0.20版本的。