性能分析对于理解某些查询为何表现出特定的性能特征至关重要。DuckDB 包含多个内置功能以支持查询性能分析,本页将对此进行介绍。有关使用 EXPLAIN 的高阶示例,请参阅“检查查询计划”页面。
语句
EXPLAIN 语句
性能分析查询的第一步通常包括检查查询计划。EXPLAIN 语句会显示查询计划并描述其底层运行机制。
EXPLAIN ANALYZE 语句
查询计划有助于开发者理解查询的性能特征。然而,通常还需要检查各个算子的性能数据以及流经这些算子的基数(cardinality)。EXPLAIN ANALYZE 语句可以获取这些信息,因为它在以易读格式打印查询计划的同时还会实际执行查询。因此,它能够提供真实的运行时性能数据。
FORMAT 选项
EXPLAIN [ANALYZE] 语句支持导出为多种格式:
text– 默认的 ASCII 艺术风格输出graphviz– 生成 DOT 输出,可使用 Graphviz 进行渲染html– 生成 HTML 输出,可使用 treeflex 进行渲染json– 生成 JSON 输出
若要指定格式,请使用 FORMAT 标签。
EXPLAIN (FORMAT html) SELECT 42 AS x;
Pragmas
DuckDB 支持多种 pragma 用于开启/关闭性能分析,并控制分析输出的详细程度。
可以使用 PRAGMA 或 SET 来设置以下 pragma。也可以使用 RESET 加设置名称来重置它们。有关更多信息,请参阅 pragma 页面中的“性能分析”部分。
| 设置 | 描述 | 默认值 | 选项 |
|---|---|---|---|
enable_profiling, enable_profile |
开启性能分析 | query_tree |
query_tree, json, query_tree_optimizer, no_output |
profiling_coverage |
设置要进行分析的算子 | SELECT |
SELECT, ALL |
profiling_output |
设置性能分析输出文件 | 控制台 (Console) | 文件路径 |
profiling_mode |
切换额外的优化器和规划器指标 | standard |
standard, detailed |
custom_profiling_settings |
启用或禁用特定指标 | 除详细分析激活项之外的所有指标 | 匹配以下格式的 JSON 对象:{"METRIC_NAME": "boolean", ...}。请参阅下方的指标部分。 |
disable_profiling, disable_profile |
关闭性能分析 |
指标
查询树有两种类型的节点:QUERY_ROOT(查询根)和 OPERATOR(算子)节点。QUERY_ROOT 仅指顶层节点,其包含的指标是针对整个查询进行测量的。OPERATOR 节点指查询计划中的各个算子。某些指标仅适用于 QUERY_ROOT 节点,而另一些则仅适用于 OPERATOR 节点。下表描述了每个指标及其适用的节点类型。
除 QUERY_NAME 和 OPERATOR_TYPE 外,所有指标均可开启或关闭。
| 度量 | 返回类型 | 单位 | 查询 | 运算符 | 描述 |
|---|---|---|---|---|---|
BLOCKED_THREAD_TIME |
double |
秒 | 线程被阻塞的总时间 | ||
EXTRA_INFO |
string |
算子特有指标 | |||
LATENCY |
double |
秒 | 查询执行的总耗时 | ||
OPERATOR_CARDINALITY |
uint64 |
绝对值 | 每个算子的基数,即返回给父节点的行数。相当于算子的 ROWS_RETURNED |
||
OPERATOR_ROWS_SCANNED |
uint64 |
绝对值 | 每个算子扫描的总行数 | ||
OPERATOR_TIMING |
double |
秒 | 每个算子所花费的时间。相当于算子的 LATENCY |
||
OPERATOR_TYPE |
string |
每个算子的名称 | |||
QUERY_NAME |
string |
查询字符串 | |||
RESULT_SET_SIZE |
uint64 |
bytes | 结果集大小 | ||
ROWS_RETURNED |
uint64 |
绝对值 | 查询返回的总行数 |
累计指标
DuckDB 还支持多种可用于所有节点的累计指标。在 QUERY_ROOT 节点中,这些指标代表查询中所有算子相应指标的总和。OPERATOR 节点则代表算子自身指标与其所有子节点指标递归相加的总和。
这些累计指标可以独立启用,即使底层对应的特定指标已被禁用。下表显示了累计指标,并展示了 DuckDB 计算该累计指标所依据的基础指标。
| 度量 | 单位 | 计算累计的指标 |
|---|---|---|
CPU_TIME |
秒 | OPERATOR_TIMING |
CUMULATIVE_CARDINALITY |
绝对值 | OPERATOR_CARDINALITY |
CUMULATIVE_ROWS_SCANNED |
绝对值 | OPERATOR_ROWS_SCANNED |
CPU_TIME 用于衡量累计的算子耗时。它不包括解析、查询规划等其他阶段所花费的时间。因此,对于某些查询,QUERY_ROOT 中的 LATENCY 可能大于 CPU_TIME。
详细性能分析
当 profiling_mode 设置为 detailed 时,会启用额外的一组指标,这些指标仅在 QUERY_ROOT 节点中可用。这包括 OPTIMIZER(优化器)、PLANNER(规划器)和 PHYSICAL_PLANNER(物理规划器)指标。它们以秒为单位进行测量,并以 double 类型返回。可以单独切换每个额外的指标。
优化器指标
在 QUERY_ROOT 节点处,存在用于衡量每个优化器所花费时间的指标。这些指标仅在特定优化器开启时才可用。可用的优化项可以通过 duckdb_optimizers() 表函数进行查询。
每个优化器都有一个对应的指标,遵循以下模板:OPTIMIZER_OPTIMIZER_NAME。例如,OPTIMIZER_JOIN_ORDER 指标对应于 JOIN_ORDER 优化器。
此外,以下指标可用于支持优化器指标:
ALL_OPTIMIZERS:启用所有优化器指标,并衡量优化器父节点所花费的时间。CUMULATIVE_OPTIMIZER_TIMING:所有优化器指标的累计总和。无需开启所有优化器指标即可使用。
规划器指标
规划器负责生成逻辑计划。目前,DuckDB 在规划器中测量两个指标:
PLANNER:从解析后的 SQL 节点生成逻辑计划所花费的时间。PLANNER_BINDING:绑定逻辑计划所花费的时间。
物理规划器指标
物理规划器负责从逻辑计划生成物理计划。物理规划器支持以下指标:
PHYSICAL_PLANNER:生成物理计划所花费的时间。PHYSICAL_PLANNER_COLUMN_BINDING:将逻辑计划中的列绑定到物理列所花费的时间。PHYSICAL_PLANNER_RESOLVE_TYPES:将逻辑计划中的类型解析为物理类型所花费的时间。PHYSICAL_PLANNER_CREATE_PLAN:创建物理计划所花费的时间。
自定义指标示例
以下示例演示了如何启用自定义性能分析并将输出格式设置为 json。在第一个示例中,我们开启性能分析并将输出保存到文件。我们仅启用 EXTRA_INFO、OPERATOR_CARDINALITY 和 OPERATOR_TIMING。
CREATE TABLE students (name VARCHAR, sid INTEGER);
CREATE TABLE exams (eid INTEGER, subject VARCHAR, sid INTEGER);
INSERT INTO students VALUES ('Mark', 1), ('Joe', 2), ('Matthew', 3);
INSERT INTO exams VALUES (10, 'Physics', 1), (20, 'Chemistry', 2), (30, 'Literature', 3);
PRAGMA enable_profiling = 'json';
PRAGMA profiling_output = '/path/to/file.json';
PRAGMA custom_profiling_settings = '{"CPU_TIME": "false", "EXTRA_INFO": "true", "OPERATOR_CARDINALITY": "true", "OPERATOR_TIMING": "true"}';
SELECT name
FROM students
JOIN exams USING (sid)
WHERE name LIKE 'Ma%';
执行查询后文件的内容:
{
"extra_info": {},
"query_name": "SELECT name\nFROM students\nJOIN exams USING (sid)\nWHERE name LIKE 'Ma%';",
"children": [
{
"operator_timing": 0.000001,
"operator_cardinality": 2,
"operator_type": "PROJECTION",
"extra_info": {
"Projections": "name",
"Estimated Cardinality": "1"
},
"children": [
{
"extra_info": {
"Join Type": "INNER",
"Conditions": "sid = sid",
"Build Min": "1",
"Build Max": "3",
"Estimated Cardinality": "1"
},
"operator_cardinality": 2,
"operator_type": "HASH_JOIN",
"operator_timing": 0.00023899999999999998,
"children": [
...
第二个示例向输出中添加了详细指标。
PRAGMA profiling_mode = 'detailed';
SELECT name
FROM students
JOIN exams USING (sid)
WHERE name LIKE 'Ma%';
输出文件的内容:
{
"all_optimizers": 0.001413,
"cumulative_optimizer_timing": 0.0014120000000000003,
"planner": 0.000873,
"planner_binding": 0.000869,
"physical_planner": 0.000236,
"physical_planner_column_binding": 0.000005,
"physical_planner_resolve_types": 0.000001,
"physical_planner_create_plan": 0.000226,
"optimizer_expression_rewriter": 0.000029,
"optimizer_filter_pullup": 0.000002,
"optimizer_filter_pushdown": 0.000102,
...
"optimizer_column_lifetime": 0.000009999999999999999,
"rows_returned": 2,
"latency": 0.003708,
"cumulative_rows_scanned": 6,
"cumulative_cardinality": 11,
"extra_info": {},
"cpu_time": 0.000095,
"optimizer_build_side_probe_side": 0.000017,
"result_set_size": 32,
"blocked_thread_time": 0.0,
"query_name": "SELECT name\nFROM students\nJOIN exams USING (sid)\nWHERE name LIKE 'Ma%';",
"children": [
{
"operator_timing": 0.000001,
"operator_rows_scanned": 0,
"cumulative_rows_scanned": 6,
"operator_cardinality": 2,
"operator_type": "PROJECTION",
"cumulative_cardinality": 11,
"extra_info": {
"Projections": "name",
"Estimated Cardinality": "1"
},
"result_set_size": 32,
"cpu_time": 0.000095,
"children": [
...
查询图
也可以将性能分析结果渲染为查询图。查询图以直观方式呈现查询计划,展示算子及其关系。查询计划必须以 json 格式输出并存储到文件中。将性能分析结果写入指定文件后,可以使用 Python 脚本将其渲染为查询图。该脚本需要安装 duckdb Python 模块。它会生成一个 HTML 文件并在您的 Web 浏览器中打开。
python -m duckdb.query_graph /path/to/file.json
查询计划中的符号
在查询计划中,哈希连接 (Hash Join) 算子遵循以下约定:连接的探测侧 (probe side) 是左操作数,而构建侧 (build side) 是右操作数。
查询计划中的连接算子会显示所使用的连接类型:
- 内连接记为
INNER。 - 左外连接和右外连接分别记为
LEFT和RIGHT。 - 全外连接记为
FULL。
提示:要可视化查询计划,请考虑使用由 图宾根大学数据库系统研究小组开发的 DuckDB 执行计划可视化工具。