性能分析对于帮助理解为什么某些查询表现出特定的性能特征至关重要。
DuckDB 包含多个内置功能以启用查询性能分析,本页面将介绍这些功能。
有关使用 EXPLAIN 的高级示例,请参阅 “检查查询计划”页面。
有关深入的解释,请参阅开发者文档中的 “性能分析”页面。
EXPLAIN 语句
分析查询的第一步可以包括检查查询计划。
EXPLAIN 语句显示查询计划并描述其背后的操作。
EXPLAIN ANALYZE 语句
查询计划帮助开发者理解查询的性能特征。
然而,通常还需要检查各个操作符的性能数据以及通过它们的基数。
EXPLAIN ANALYZE 语句可以获取这些信息,因为它不仅漂亮地打印查询计划,还执行查询。
因此,它提供了实际的运行时性能数据。
Pragmas
DuckDB 支持多种 pragma 来开启和关闭性能分析,并控制性能分析输出的详细程度。
以下是一些可用的编译指示,可以使用PRAGMA或SET来设置。
它们也可以使用RESET后跟设置名称来重置。
更多信息,请参阅编译指示页面的“性能分析”部分。
| 设置 | 描述 | 默认值 | 选项 |
|---|---|---|---|
enable_profiling, enable_profile |
开启性能分析。 | query_tree |
query_tree, json, query_tree_optimizer, no_output |
profiling_output |
设置一个性能分析输出文件。 | 控制台 | 一个文件路径。 |
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 |
absolute | ✅ | 每个操作符扫描的总行数。 | |
OPERATOR_TIMING |
double |
秒 | ✅ | 每个操作符所花费的时间。操作符等效于LATENCY。 |
|
OPERATOR_TYPE |
string |
✅ | 每个操作符的名称。 | ||
QUERY_NAME |
string |
✅ | 查询字符串。 | ||
RESULT_SET_SIZE |
uint64 |
字节 | ✅ | ✅ | 结果的大小。 |
ROWS_RETURNED |
uint64 |
absolute | ✅ | 查询返回的行数。 |
累计指标
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: 启用所有优化器指标,并测量优化器父节点所花费的时间。CUMMULATIVE_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文件并在您的网络浏览器中打开它。
python -m duckdb.query_graph /path/to/file.json
查询计划中的符号
在查询计划中,哈希连接操作符遵循以下约定: 连接的探测端是左操作数,而构建端是右操作数。
查询计划中的连接运算符显示使用的连接类型:
- 内连接表示为
INNER。 - 左外连接和右外连接分别表示为
LEFT和RIGHT。 - 全外连接表示为
FULL。