前言
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的执行过程是
- 对t2表进行scan和过滤,t2是驱动表
- 将结果和t1表进行nest loop join,join条件是 t1.a1 = t2.b2
- 对join的结果进行聚集计算,计算的通过临时表进行的,聚集结果也保存在临时表中
- 对临时表的内容进行排序
- 对排序结果limit 10输出
这个例子的Iterator Tree的样子是:
|-> SortingIterator
|-> TableScanIterator
|-> TemptableAggregateIterator
|-> NestLoopIterator
|-> FilterIterator
| |-> TableScanIterator
|-> FilterIterator
|-> TableScanIterator
RowIterator是所有Iterator的基类,下面列出了explain相关的主要成员和方法:
class RowIterator {
public:
struct Child {
RowIterator *iterator;
std::string description;
};
virtual std::vector<Child> children() const { return std::vector<Child>(); } // 返回iterator的children list,在递归中使用
virtual std::vector<std::string> DebugString() = 0; // 输出Iterator的计划信息,不同的Iterator会有各自的实现
JOIN *join_for_explain() const { return m_join_for_explain; } // 如果是join的root_iterator会返回join的指针,用来遍历join上的子查询并输出子查询的查询计划。
private:
double m_estimated_cost = -1.0; // Iterator执行完成预期的代价
double m_expected_rows = -1.0; // Iterator输出的行数
};
RowIterator::children
返回当前iterator的children list,包装结构为Child,部分Iterator在实现该方法时除了返回childrend iterator的指针以外,还返回了description。 例如:
Iterator tree的结构如下:
HashJoinIterator
--> TableScanIterator // t2, probe side
--> TableScanIterator // t1, build side
HashJoinIterator的children方法返回的内容如下:
{<TableScanIterator(t2), null>,
<TableScanIterator(t1), "Hash">}
产生Iterator的格式化字符串, 每个Iterator都有自己的实现,主要是描述Iterator的行为,包括Table Scan的方式, 过滤的Condition,Join method等等, 通常都是一行。
Explain Format Tree 介绍
执行过程
Explain
std::string PrintQueryPlan(int level, RowIterator *iterator) {
string ret;
if (iterator == nullptr) {
ret.assign(level * 4, ' ');
return ret + "<not executable by iterator executor>\n";
int top_level = level; // 当前缩进level, 每个level缩进4个空格
// 输出自身的计划信息
for (const string &str : FullDebugString(current_thd, *iterator)) {
ret.append(level * 4, ' ');
ret += "-> ";
ret += str;
ret += "\n";
++level;
// 遍历所有的children iterator, 递归输出计划信息
for (const RowIterator::Child &child : iterator->children()) {
if (!child.description.empty()) {
ret.append(level * 4, ' ');
ret.append("-> ");
ret.append(child.description);
ret.append("\n");
ret += PrintQueryPlan(level + 1, child.iterator);
} else {
ret += PrintQueryPlan(level, child.iterator);
}
}
//
if (iterator->join_for_explain() != nullptr) {
for (const auto &child :
GetIteratorsFromSelectList(iterator->join_for_explain())) {
ret.append(top_level * 4, ' ');
ret.append("-> ");
ret.append(child.description);
ret.append("\n");
ret += PrintQueryPlan(top_level + 1, child.iterator);
}
}
return ret;
}
FullDebugString()
通过本方法输出一个iterator的完整的计划信息,包括iterator本身的信息,cost信息和执行信息
vector<string> FullDebugString(const THD *thd, const RowIterator &iterator) {
vector<string> ret = iterator.DebugString(); // 生成iterator的计划信息
if (iterator.expected_rows() >= 0.0) { // 生成cost info
// NOTE: We cannot use %f, since MSVC and GCC round 0.5 in different
// directions, so tests would not be reproducible between platforms.
// Format/round using my_gcvt() and llrint() instead.
char cost_as_string[FLOATING_POINT_BUFFER];
my_fcvt(iterator.estimated_cost(), 2, cost_as_string, /*error=*/nullptr);
char str[512];
snprintf(str, sizeof(str), " (cost=%s rows=%lld)", cost_as_string,
llrint(iterator.expected_rows()));
ret.back() += str;
}
if (iterator.expected_rows() < 0.0) {
// We always want a double space between the iterator name and the costs.
ret.back().push_back(' ');
ret.back().push_back(' ');
ret.back() += iterator.TimingString();
}
return ret;
}
PolarDB的Parallel Query功能引入了新的exchange算子, 同时多阶段并行计划在生成计划的过程中是基于Cost选择的最优计划,我们对Parallel Query计划Format Tree的输出信息进行了完善和补充, 下面是Parallel Query计划的Explain Format Tree的一个简单例子。
- 对 group by/ distinct / order by / window / limit iterator增加了代价显示的支持
- 新增了exchange算子和exchange算子详细信息的显示,包括算子类型(gather/repartition/nroadcast), slice id, parallel worker number。 对于repartition的exchange算子,同时还显示了repartition column。
附录
RowIterator <|--- TimingIterator
|- UnqualifiedCountIterator
|- FakeSingleRowIterator
|- ZeroRowsIterator
|- ZeroRowsAggregatedIterator
|- FollowTailIterator
|- FilterIterator
|- LimitOffsetIterator
|- AggregateIterator
|- PrecomputedAggregateIterator
|- NestedLoopIterator
|- CacheInvalidatorIterator
|- WeedoutIterator
|- RemoveDuplicatesIterator
|- NestedLoopSemiJoinWithDuplicateRemovalIterator
|- WindowingIterator
|- BufferingWindowingIterator
|- MaterializeInformationSchemaTableIterator
|- AppendIterator
|- HashJoinIterator
|- SortingIterator
|- TableRowIterator <|--- TableScanIterator
|- IndexScanIterator
|- IndexRangeScanIterator
|- SortBufferIterator
|- SortBufferIndirectIterator
|- SortFileIterator
|- SortFileIndirectIterator
|- MaterializeIterator
|- StreamingIterator
|- TemptableAggregateIterator
|- MaterializedTableFunctionIterator
|- RefIterator
|- RefOrNullIterator
|- EQRefIterator
|- ConstIterator
|- FullTextSearchIterator
|- DynamicRangeIterator
|- PushedJoinRefIterator
注:文中引用代码是基于mysql-8.0.20版本的。