基于湖库一体架构,统一管理结构化、半结构化与非结构化等多模态数据,一个系统承载事务处理、实时分析与 AI 工作负载。
说明
- obdiag 部署简单,可以部署到任一台能够连接到集群各节点的服务器上,并不局限于 OBServer 节点。
- obdiag 具备集中式收集的特点,程序单点部署,无需每台服务器部署。使用时只需要在部署机器上执行收集、巡检或分析命令即可。
更新时间:2026-07-28
在 OceanBase 数据库的运维过程中,面对复杂的 SQL 执行问题,如并行 SQL 或慢 SQL,快速定位问题根源至关重要。obdiag 作为一款专为 OceanBase 数据库设计的黑屏诊断工具,能够帮助运维人员高效进行 SQL 性能分析。本文将详细介绍如何使用 obdiag 一键收集并行 SQL 和慢 SQL 的诊断信息,从而进行精准的问题定位和性能优化。
本文档适用于 OceanBase 数据库所有版本和 obdiag V2.0.0 及以上版本。
obdiag 支持两种部署方式:独立部署和使用 obd 进行部署。本文使用的集群未使用 obd 进行部署,因此采用独立部署方式来安装 obdiag。具体命令如下:
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/oceanbase/OceanBase.repo
sudo yum install -y oceanbase-diagnostic-tool
source /usr/local/oceanbase-diagnostic-tool/init.sh
关于 obdiag 安装部署相关的更多信息,请参考 obdiag 安装。
obdiag config -hxx.xx.xx.xx -uroot@sys -Pxxxx -p*****
关于 obdiag 配置相关的更多信息,请参考 obdiag 配置。
可以从 GV$OB_SQL_AUDIT 获取或通过 SELECT last_trace_id(); 获取。
从 GV$OB_SQL_AUDIT 获取:
select query_sql,trace_id from oceanbase.GV$OB_SQL_AUDIT where query_sql like 'xxx%' order by REQUEST_TIME desc limit 5;
其中,xxx% 是一个通配符表达式,用于在 query_sql 字段中查找以 xxx 开头的 SQL 语句,请根据实际情况填写。
OceanBase 数据库 V4.0.0 以下版本可从 GV$SQL_AUDIT 查看 trace_id,OceanBase 数据库 V4.0.0 及以上版本可从 GV$OB_SQL_AUDIT 查看 trace_id。
从当前 Session 执行 SELECT last_trace_id(); 获取:
SELECT last_trace_id();
需确保待诊断 SQL 为执行 SELECT last_trace_id(); 命令的前一条 SQL。
obdiag gather plan_monitor [options]
选项说明如下:
| 选项名 | 是否必选 | 数据类型 | 默认值 | 说明 |
|---|---|---|---|---|
| --trace_id | 是 | string | 默认为空 | OceanBase 数据库 V4.0.0 以下版本可从 GV$SQL_AUDIT 查看 trace_id,OceanBase 数据库 V4.0.0 及以上版本可从 GV$OB_SQL_AUDIT 查看 trace_id。 |
| --store_dir | 否 | string | 默认为命令执行的当前路径 | 存储结果的本地路径。 |
| -c | 否 | string | ~/.obdiag/config.yml | 配置文件路径。 |
| --env | 否 | string | 默认为空 | 待分析 trace_id 涉及的 SQL 所在的业务租户的连接串,主要用于获取 explain SQL 报告。 |
需要确保已经在 obdiag 配置文件 config.yml 中配置好需要收集的集群 sys 连接信息。关于 obdiag 配置相关的更多信息,请参见 obdiag 配置。
示例:
obdiag gather plan_monitor --trace_id YB420BA2D99B-0005EBBFC45D5A00-0-0 --env "{db_connect='-hxx -Pxx -uxx -pxx -Dxx'}"
gather_plan_monitor start ...
Gather Sql Plan Monitor Summary:
+-----------+-----------+--------+-------------------------------------+
| Cluster | Status | Time | PackPath |
+===========+===========+========+=====================================+
| obcluster | Completed | 2 s | ./obdiag_gather_pack_20240611171324 |
+-----------+-----------+--------+-------------------------------------+
创建测试表。
create table game (round int primary key, team varchar(10), score int)
partition by hash(round) partitions 3;
# 插入数据
insert into game values (1, "CN", 4), (2, "CN", 5), (3, "JP", 3);
insert into game values (4, "CN", 4), (5, "US", 4), (6, "JP", 4);
执行并行 SQL 并获取 trace_id。
obclient [oceanbase]> select /*+ parallel(3) */ team, sum(score) total from game group by team;
+------+-------+
| team | total |
+------+-------+
| US | 4 |
| CN | 13 |
| JP | 7 |
+------+-------+
3 rows in set (0.006 sec)
obclient [oceanbase]> SELECT last_trace_id();
+-----------------------------------+
| last_trace_id() |
+-----------------------------------+
| YB420BA1CC63-00061E5E2BA8301B-0-0 |
+-----------------------------------+
1 row in set (0.000 sec)
一键收集诊断信息。
$obdiag gather plan_monitor --trace_id YB420BA1CC63-00061E5E2BA8301B-0-0 --env "{db_connect='-hxxx.xxx.xxx.xxx -Pxxx -uxxx -pxxx -Dxxx'}"
gather_plan_monitor start ...
table count (('game', 0),)
data size (('xxx.xxx.xxx.xxx', 'LEADER', 0),)
Gather Sql Plan Monitor Summary:
+-----------+-----------+--------+-------------------------------------+
| Cluster | Status | Time | PackPath |
+===========+===========+========+=====================================+
| obcluster | Completed | 3 s | ./obdiag_gather_pack_20240808114502 |
+-----------+-----------+--------+-------------------------------------+
Trace ID: 97b2594c-5538-11ef-ab2c-00163e06beb9
If you want to view detailed obdiag logs, please run: obdiag display-trace 97b2594c-5538-11ef-ab2c-00163e06beb9
收集到的诊断信息保存在 obdiag_gather_pack_20240808114502 文件中,部分文件目录展示如下:
#tree
.
├── resources
│ └── web
│ ├── bootstrap.min.css
│ ├── bootstrap.min.js
│ ├── jquery-3.2.1.min.js
│ └── popper.min.js
├── result_summary.txt
└── sql_plan_monitor_report.html
50 directories, 312 files
其中,sql_plan_monitor_report.html 为最终结果,可使用 scp 命令传输到本地之后,通过浏览器查看完整的报告。
使用浏览器打开时,需将 resources 文件夹一并放到目录下,否则打开的结果中看不到前端样式。
完整的报告展示如下:

可参考下文 SQL Monitor Report 解读章节,进行详细的性能分析。
SQL Monitor Report 表头展示的是从 GV$OB_SQL_AUDIT 获取的基本的 SQL 执行信息,下文将针对每个部分进行详细说明。

执行计划信息展示如下:

执行计划由两部分组成,一部分是 explain extended [SQL语句] 的结果,我们称之为物理执行计划,并不是这条 SQL 实际执行时命中的执行计划,物理执行计划下面才是实际执行计划。
物理执行计划可以看到统计信息、Hint、Outline Data 等信息。物理执行计划和实际执行计划大部分情况下是一样的,如果不一样,需要关注一下。
点击蓝色字体“SCHEMA 信息”,可对这部分信息进行展开和折叠。如下图所示,需要重点关注“表行数”是否与执行计划里的 explain extended [SQL] 结果对应。如果数量级存在误差,则大概率统计信息收集存在滞后,需要先解决统计信息收集的问题,再去看 SQL 慢的问题。

点击蓝色字体“SQL_AUDIT 信息”,可对这部分信息进行展开和折叠。

使用的 SQL 如下:
-- ob 4.x --
select /*+ sql_audit */
`SVR_IP`,`SVR_PORT`,`REQUEST_ID`,`SQL_EXEC_ID`,`TRACE_ID`,`SID`,`CLIENT_IP`,`CLIENT_PORT`,`TENANT_ID`,
`EFFECTIVE_TENANT_ID`,`TENANT_NAME`,`USER_ID`,`USER_NAME`,`USER_CLIENT_IP`,`DB_ID`,`DB_NAME`,`SQL_ID`,
`QUERY_SQL`,`PLAN_ID`,`AFFECTED_ROWS`,`RETURN_ROWS`,`PARTITION_CNT`,`RET_CODE`,`QC_ID`,`DFO_ID`,`SQC_ID`,
`WORKER_ID`,`EVENT`,`P1TEXT`,`P1`,`P2TEXT`,`P2`,`P3TEXT`,`P3`,`LEVEL`,`WAIT_CLASS_ID`,`WAIT_CLASS`,`STATE`,
`WAIT_TIME_MICRO`,`TOTAL_WAIT_TIME_MICRO`,`TOTAL_WAITS`,`RPC_COUNT`,`PLAN_TYPE`,`IS_INNER_SQL`,
`IS_EXECUTOR_RPC`,`IS_HIT_PLAN`,`REQUEST_TIME`,`ELAPSED_TIME`,`NET_TIME`,`NET_WAIT_TIME`,`QUEUE_TIME`,
`DECODE_TIME`,`GET_PLAN_TIME`,`EXECUTE_TIME`,`APPLICATION_WAIT_TIME`,`CONCURRENCY_WAIT_TIME`,
`USER_IO_WAIT_TIME`,`SCHEDULE_TIME`,`ROW_CACHE_HIT`,`BLOOM_FILTER_CACHE_HIT`,`BLOCK_CACHE_HIT`,
`DISK_READS`,`RETRY_CNT`,`TABLE_SCAN`,`CONSISTENCY_LEVEL`,`MEMSTORE_READ_ROW_COUNT`,
`SSSTORE_READ_ROW_COUNT`,`REQUEST_MEMORY_USED`,`EXPECTED_WORKER_COUNT`,`USED_WORKER_COUNT`,
`TX_ID`,`REQUEST_TYPE`,`IS_BATCHED_MULTI_STMT`,`OB_TRACE_INFO`,`PLAN_HASH`
from oceanbase.gv$ob_sql_audit where trace_id = '%s' " "AND client_ip IS NOT NULL ORDER BY QUERY_SQL ASC, REQUEST_ID
可参考如下时间轴间隔查看每个环节的耗时情况:

SQL_AUDIT 信息部分,可以重点关注以下字段:
PLAN_TYPE:可通过该字段看到该 SQL 的执行计划类型:
一般情况下,如果远程执行比较多,可能是出现了切主或 ODP 客户端路由不准的情况。
RETRY_CNT:可通过该字段查看 Retry 次数。如果 Retry 次数很多,则需要考虑是否有锁冲突或切主等情况。
QUEUE_TIME:可通过该字段查看 Queue Time 是不是很大。
GET_PLAN_TIME:可通过该字段查看执行计划时间。如果时间很长,一般会伴随 IS_HIT_PLAN = 0, 表示没有命中 Plan Cache。
EXECUTE_TIME:可通过该字段查看执行时间。如果执行时间很长,则:
SSSTORE_READ_ROW_COUNT、MEMSTORE_READ_ROW_COUNT 这两个字段查看访问的行数是否很多, 比如大小账号场景可能导致 RT 抖动。点击蓝色字体“SQL_PLAN_MONITOR DFO 级调度时序汇总”,可对这部分信息进行展开和折叠。

并行执行使用“生产者-消费者”模型来进行流水执行。并行调度算子解析计划,将它们切分成多个操作步骤,每个操作步骤称之为一个 DFO(Data Flow Operation),即子计划。每个 DFO 包含若干个串行执行的算子,例如,一个 DFO 里包含了扫描分区、聚集、发送算子,另外一个 DFO 里包含了收集、聚集算子等。
obdiag 封装的 SQL 如下:
-- DFO 级
select
AVG(ROWS) EST_ROWS, /*0*/
plan_monitor.PLAN_DEPTH PLAN_DEPTH,
plan_monitor.PLAN_LINE_ID PLAN_LINE_ID,
PLAN_OPERATION,
COUNT(*) PARALLEL,
MIN(FIRST_REFRESH_TIME) MIN_FIRST_REFRESH_TIME,/*5*/
MAX(LAST_REFRESH_TIME) MAX_LAST_REFRESH_TIME,
MIN(FIRST_CHANGE_TIME) MIN_FIRST_CHANGE_TIME,
MAX(LAST_CHANGE_TIME) MAX_LAST_CHANGE_TIME,
UNIX_TIMESTAMP(MIN(FIRST_REFRESH_TIME)) MIN_FIRST_REFRESH_TS,
UNIX_TIMESTAMP(MAX(LAST_REFRESH_TIME)) MAX_LAST_REFRESH_TS, /*10*/
UNIX_TIMESTAMP(MIN(FIRST_CHANGE_TIME)) MIN_FIRST_CHANGE_TS,
UNIX_TIMESTAMP(MAX(LAST_CHANGE_TIME)) MAX_LAST_CHANGE_TS,
AVG(TIMESTAMPDIFF(MICROSECOND, FIRST_REFRESH_TIME, LAST_REFRESH_TIME)) AVG_REFRESH_TIME,
MAX(TIMESTAMPDIFF(MICROSECOND, FIRST_REFRESH_TIME, LAST_REFRESH_TIME)) MAX_REFRESH_TIME,
MIN(TIMESTAMPDIFF(MICROSECOND, FIRST_REFRESH_TIME, LAST_REFRESH_TIME)) MIN_REFRESH_TIME, /*15 */
AVG(TIMESTAMPDIFF(MICROSECOND, FIRST_CHANGE_TIME, LAST_CHANGE_TIME)) AVG_CHANGE_TIME,
MAX(TIMESTAMPDIFF(MICROSECOND, FIRST_CHANGE_TIME, LAST_CHANGE_TIME)) MAX_CHANGE_TIME,
MIN(TIMESTAMPDIFF(MICROSECOND, FIRST_CHANGE_TIME, LAST_CHANGE_TIME)) MIN_CHANGE_TIME,
SUM(OUTPUT_ROWS) TOTAL_OUTPUT_ROWS,
(MAX(TIMESTAMPDIFF(MICROSECOND, FIRST_CHANGE_TIME, LAST_CHANGE_TIME)) - MIN(TIMESTAMPDIFF(MICROSECOND, FIRST_CHANGE_TIME, LAST_CHANGE_TIME))) / MAX(TIMESTAMPDIFF(MICROSECOND, FIRST_CHANGE_TIME, LAST_CHANGE_TIME)+0.00000001) SKEWNESS,
SUM(STARTS) TOTAL_RESCAN_TIMES,/* 20 */
MAX(OTHERSTAT_1_ID) OTHERSTAT_1_ID,
SUM(OTHERSTAT_1_VALUE) SUM_STAT_1,
MAX(OTHERSTAT_1_VALUE) MAX_STAT_1,
MIN(OTHERSTAT_1_VALUE) MIN_STAT_1,
AVG(OTHERSTAT_1_VALUE) AVG_STAT_1, /* 25 */
MAX(OTHERSTAT_2_ID) OTHERSTAT_2_ID,
SUM(OTHERSTAT_2_VALUE) SUM_STAT_2,
MAX(OTHERSTAT_2_VALUE) MAX_STAT_2,
MIN(OTHERSTAT_2_VALUE) MIN_STAT_2,
AVG(OTHERSTAT_2_VALUE) AVG_STAT_2, /* 30 */
MAX(OTHERSTAT_3_ID) OTHERSTAT_3_ID,
SUM(OTHERSTAT_3_VALUE) SUM_STAT_3,
MAX(OTHERSTAT_3_VALUE) MAX_STAT_3,
MIN(OTHERSTAT_3_VALUE) MIN_STAT_3,
AVG(OTHERSTAT_3_VALUE) AVG_STAT_3, /* 35 */
MAX(OTHERSTAT_4_ID) OTHERSTAT_4_ID,
SUM(OTHERSTAT_4_VALUE) SUM_STAT_4,
MAX(OTHERSTAT_4_VALUE) MAX_STAT_4,
MIN(OTHERSTAT_4_VALUE) MIN_STAT_4,
AVG(OTHERSTAT_4_VALUE) AVG_STAT_4, /* 40 */
MAX(OTHERSTAT_5_ID) OTHERSTAT_5_ID,
SUM(OTHERSTAT_5_VALUE) SUM_STAT_5,
MAX(OTHERSTAT_5_VALUE) MAX_STAT_5,
MIN(OTHERSTAT_5_VALUE) MIN_STAT_5,
AVG(OTHERSTAT_5_VALUE) AVG_STAT_5, /* 45*/
MAX(OTHERSTAT_6_ID) OTHERSTAT_6_ID,
SUM(OTHERSTAT_6_VALUE) SUM_STAT_6,
MAX(OTHERSTAT_6_VALUE) MAX_STAT_6,
MIN(OTHERSTAT_6_VALUE) MIN_STAT_6,
AVG(OTHERSTAT_6_VALUE) AVG_STAT_6 /* 50 */,
TRUNCATE(AVG(DB_TIME)/1000000000.0/2.5, 4) MY_DB_TIME,
TRUNCATE(AVG(DB_TIME-USER_IO_WAIT_TIME)/1000000000.0/2.5, 4) MY_CPU_TIME,
TRUNCATE(AVG(USER_IO_WAIT_TIME)/1000000000.0/2.5, 4) MY_IO_TIME
from
(
select * FROM oceanbase.gv$sql_plan_monitor
where
trace_id = 'YF2A0BA2DA7E-00061D6A8ADDA95A-0-0'
) plan_monitor
LEFT JOIN
(
SELECT ROWS, PLAN_LINE_ID FROM oceanbase.gv$ob_plan_cache_plan_explain WHERE plan_id = xxx AND tenant_id = xxxx and svr_ip = 'xxxx' and svr_port = xxxx
) plan_explain
ON
plan_monitor.PLAN_LINE_ID = plan_explain.PLAN_LINE_ID
GROUP BY
plan_monitor.PLAN_LINE_ID, plan_monitor.PLAN_OPERATION
ORDER BY
plan_monitor.PLAN_LINE_ID ASC;
点击蓝色字体“SQL_PLAN_MONITOR SQC 级汇总”,可对这部分信息进行展开和折叠。

该部分涉及到的概念说明如下:
QC:即 Query Coordinator(查询协调者)。当用户给定的 SQL 语句需要访问的数据位于 2 台或 2 台以上 OBServer 节点、或者单节点包含多个分区的表,就会启用并行执行。当 SQL 计划在连接的 OBserver 节点执行到并行查询时,主进程在决定并行度后,发送工作线程获取请求到各个机器,此时用户连接的 OBServer 节点就会作为 QC 角色。
SQC:即 Sub Query Coordinator(辅助查询协调者)。每个处理 QC 请求的线程,自动成为该查询的 SQC。SQC 负责为所在 OBServer 节点上各个 DFO 申请执行资源、构造执行上下文环境等,然后启动 DFO 在各个 OBServer 节点上并行执行。
点击蓝色字体“SQL_PLAN_MONITOR 详情”,可对这部分信息进行展开和折叠。

这部分主要是算子展开和线程展开,可以看到哪些算子耗费时间比较长,哪些线程耗费时间比较多。同时也可以看到哪些线程是空闲的,如果大部分线程都是空闲状态,即便加了并行,可能也不奏效,因此需要查看并行设置是否正确。
以下为实际业务场景中,用户执行的 SQL 信息:
SELECT
`t`.`date` AS `date`,
SUM(`t`.`adcost`) AS `adcost`,
SUM(`t`.`ns`) AS `ns`,
SUM(`t`.`nc`) AS `nc`,
SUM(`t`.`realtimeordernum`) AS `realtimeordernum`,
SUM(`t`.`realtimeorderprice`) AS `realtimeorderprice`
FROM
(select * from `galileo`.`report_game_consume_conversion` where `date` BETWEEN '2024-07-21' AND '2024-07-22') AS `t`
JOIN (
SELECT
`date` AS `date1`,
`solutionid`
FROM
`galileo`.`report_game_solution`
WHERE
`date` BETWEEN '2024-07-21' AND '2024-07-22'
) AS `t1` ON `t1`.`date1`=`t`.`date`
AND `t`.`solutionid`=`t1`.`solutionid`
JOIN (
SELECT
`date` AS `date2`,
`adspaceid`
FROM
`galileo`.`report_game_adspace`
WHERE
`date` BETWEEN '2024-07-21' AND '2024-07-22'
) AS `t2` ON `t2`.`date2`=`t`.`date`
AND `t`.`adspaceid`=`t2`.`adspaceid`
LEFT JOIN (
SELECT
`date` AS `date3`,
`solutionid`,
`adspaceid`
FROM
`galileo`.`report_game_order`
WHERE
`date` BETWEEN '2024-07-21' AND '2024-07-22'
GROUP BY
`date`,
`solutionid`,
`adspaceid`
) AS `t3` ON `t3`.`date3`=`t`.`date`
AND `t`.`solutionid`=`t3`.`solutionid`
AND `t`.`adspaceid`=`t3`.`adspaceid`
GROUP BY
`t`.`date` limit 10
执行过程中,发现 SQL 比较慢,因此使用上文提到的诊断方法去收集 SQL 诊断信息。收集到的报告截图如下:

从报告可以看出,估行和吐行相差较大。回看报告中的 SCHEMA 信息,发现实际的数据行数有百万级别,这也证明了算子吐行是准确的,估行相差很大。这种估行不准非常容易误导优化器,导致优化器给出错误的执行计划。
基于上述诊断信息,我们可以来看一下执行计划,发现有三个 PHY_NESTED_LOOP_JOIN 算子。三张表都是百万级别的吐行,使用 PHY_NESTED_LOOP_JOIN 算子非常耗时,不如使用 Hash Join。
接下来,我们查看一下 SQL,发现进行了多表关联,此时我们可以直接通过 Hint 来调整算子:
/*+
LEADING(@"SEL$1" (("galileo"."report_game_solution"@"SEL$3" "galileo"."report_game_consume_conversion"@"SEL$2") "galileo"."report_game_adspace"@"SEL$4"))
USE_HASH(@"SEL$1" "galileo"."report_game_adspace"@"SEL$4") |
USE_HASH(@"SEL$1" "galileo"."report_game_consume_conversion"@"SEL$2")
*/
关于 Hint 的更多内容,请参考 Optimizer Hint。
如果不知道 Hint 怎么写,可以参考 explain extended [SQL] 的结果,根据实际情况进行修改。

加完 Hint 之后,执行结果 4s 输出,输出时间从 300s 减少到 4s,性能明显提升。
关于 obdiag 的更多信息,请参见 obdiag 官网文档。