OceanBase 诊断调优—— (保姆级教程)帮你快速收集和解读 SQL 的DBMS_XPLAN 信息

1. 前言

DBMS_XPLAN 系统包提供了逻辑计划管理相关的功能,包括逻辑计划优化追踪等。非常适合排查单条 SQL 性能问题。下表列出了 OceanBase 数据库当前版本所支持的 DBMS_XPLAN 子程序和简要描述。

子程序描述
DISPLAY_ACTIVE_SESSION_PLAN用于展示指定 Session 的实时计划详情。
DISPLAY_CURSOR用于展示已执行的查询计划详情。
DISPLAY查询并格式化历史的 EXPLAIN 计划。
DISABLE_OPT_TRACE关闭当前 Session 的优化器全链路追踪功能。
DISPLAY_SQL_PLAN_BASELINE查看 SPM 的基线计划。
ENABLE_OPT_TRACE开启当前 Session 的优化器全链路追踪。
SET_OPT_TRACE_PARAMETER修改当前 Session 的优化器全链路追踪的参数。

想了解的大家具体子程序的大家可以点开链接具体去看,本文主要是从给大家提效的角度,帮助大家通过obdiag 快速获取我们一般排查 SQL 性能问题的所需要的DBMS_XPLAN信息,以及如何去解读DBMS_XPLAN的内容。

2. 使用说明

2.1. 安装并配置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
sh /opt/oceanbase-diagnostic-tool/init.sh

# 配置被诊断集群信息
obdiag config -hxx.xx.xx.xx -uroot@sys -Pxxxx -p*****

更多 obdiag 内容参见官网: https://www.oceanbase.com/docs/obdiag-cn

2.2. 命令说明

obdiag gather dbms_xplan [options]

选项说明如下:

选项名是否必选数据类型默认值说明
--trace_idstring默认为空OceanBase 数据库 V4.0.0 以下版本可从 gv$sql_audit 中查看 trace_id,OceanBase 数据库 V4.0.0 及以上版本可从 gv$ob_sql_audit 中查看 trace_id。
--scopestringall选择收集 SQL 的性能诊断信息时所用的 DBMS_XPLAN 系统包,可配置值如下:opt_tracedisplay_cursor:all
--userstring默认为空待收集 SQL 的所在租户的用户名。
--passwordstring默认为空待收集 SQL 所在租户的用户密码。
--store_dirstring默认为命令执行的当前路径存储结果的本地路径。
-cstring~/.obdiag/config.yml配置文件路径。
--inner_configstring默认为空obdiag 自用的配置。
--configstring默认为空需被 obdiag 诊断的集群的配置,固定样式:--config key1=value1 --config key2=value2

2.3. 举个例子

  1. 建测试表
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);
  1. 执行并行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()                   |
+-----------------------------------+
| YF2A0BA2DA7E-000615B522FD3D35-0-0 |
+-----------------------------------+
1 row in set (0.000 sec)
  1. obdiag 一键收集dbms_xplan信息
$ obdiag gather dbms_xplan --user=test@sys --password=***** --trace_id=YF2A0BA2DA7E-000615B522FD3D35-0-0
obdiag version: <VERSION>
gather_dbms_xplan start ...
execute dbms_xplan.enable_opt_trace start ...
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
call dbms_xplan.enable_opt_trace();
call dbms_xplan.set_opt_trace_parameter(identifier=>'obdiag_m9IRRY', `level`=>3);
explain select /*+ parallel(3) */ team, sum(score) total from game group by team
call dbms_xplan.disable_opt_trace();
execute dbms_xplan.enable_opt_trace end

Gather dbms_xplan.enable_opt_trace:
+----------------+-----------+---------+--------+----------------------------------------------------------------------------------------------+
| Node           | Status    | Size    | Time   | PackPath                                                                                     |
+================+===========+=========+========+==============================================================================================+
| xx.xx.xx.xx    | Completed | 35.841K | 0 s    | ./obdiag_gather_pack_20250625160759/xx_xx_xx_xx_optimizer_trace_f0dfF2_obdiag_m9IRRY.trac    |
+----------------+-----------+---------+--------+----------------------------------------------------------------------------------------------+

Gather dbms_xplan.display_cursor:
+-----------+--------------------------------------------------------------------------+--------+
| Status    | Result Details                                                           | Time   |
+===========+==========================================================================+========+
| Completed | ./obdiag_gather_pack_20250625160759/obdiag_dbms_xplan_display_cursor.txt | 0.39 s |
+-----------+--------------------------------------------------------------------------+--------+


Trace ID: 8262029e-519b-11f0-a748-00163e01c7ce
If you want to view detailed obdiag logs, please run: obdiag display-trace 8262029e-519b-11f0-a748-00163e01c7ce
(base)

可以看到通过obdiag gather dbms_xplan 一条命令就搞定了 dbms_xplan.display_cursor和dbms_xplan.enable_opt_trace的信息, 非常放便,结果统一放到了./obdiag_gather_pack_20250625160759/文件夹中。

2.4. obdiag 执行流程

我们执行一下obdiag display-trace 8262029e-519b-11f0-a748-00163e01c7ce 看下obdiag 的详细日志去看下这个工具怎么拿的信息。

# 看一下dbms_xplan.enable_opt_trace()的内容,关键信息如下:
[2025-06-25 16:07:59.885] [INFO] SET TRANSACTION ISOLATION LEVEL READ COMMITTED
[2025-06-25 16:07:59.885] [INFO] call dbms_xplan.enable_opt_trace();
[2025-06-25 16:08:00.083] [INFO] call dbms_xplan.set_opt_trace_parameter(identifier=>'obdiag_m9IRRY', `level`=>3);
[2025-06-25 16:08:00.096] [INFO] explain select /*+ parallel(3) */ team, sum(score) total from game group by team
[2025-06-25 16:08:00.104] [INFO] call dbms_xplan.disable_opt_trace();

# dbms_xplan.display_cursor 的内容,关键信息如下:
[2025-06-25 16:08:00.135] [DEBUG] - execute SQL: SELECT DBMS_XPLAN.DISPLAY_CURSOR(273900, 'all', '192.168.1.11',  3882, 1) FROM DUAL
[2025-06-25 16:08:00.174] [DEBUG] - dbms_xplan.display_cursor report complete
[2025-06-25 16:08:00.175] [INFO]
[2025-06-25 16:08:00.175] [INFO] Gather dbms_xplan.display_cursor:
[2025-06-25 16:08:00.175] [INFO] +-----------+--------------------------------------------------------------------------+--------+
[2025-06-25 16:08:00.175] [INFO] | Status    | Result Details                                                           | Time   |
[2025-06-25 16:08:00.175] [INFO] +===========+==========================================================================+========+
[2025-06-25 16:08:00.175] [INFO] | Completed | ./obdiag_gather_pack_20250625160759/obdiag_dbms_xplan_display_cursor.txt | 0.39 s |
[2025-06-25 16:08:00.175] [INFO] +-----------+--------------------------------------------------------------------------+--------+
[2025-06-25 16:08:00.175] [INFO]

可以发现,obdiag 工具获取信息的方式和官网提供的一样,省去了大家去记命令的过程。并且大家注意看dbms_xplan.display_cursor的时候,obdiag 用的SQL语句自动填充了参数(273900, 'all', '192.168.1.11', 3882, 1),这些参数从做到右依次是plan_id、format、svr_ip、svr_port、tenant_id。这些参数是obdiag 通过传递的trace_id自动从集群中获取的,别小看了这些参数,这些参数填充才能真正获取刚才执行的SQL的详细dbms_xplan.display_cursor。这里面有个注意点,如果不指定查询计划的话,拿回来的值可能因为执行dbms_xplan.display_cursor的节点和待排查问题SQL执行的节点不是一个导致拿回来的结果不是我们想要的。

SELECT DBMS_XPLAN.DISPLAY_CURSOR(273900, 'all', '192.168.1.11',  3882, 1) FROM DUAL

所以善用工具去执行,还是能省不少事情的。

3. 结果解读

我们收集到两个结果文件

$tree
.
├── xx_xx_xx_xx_optimizer_trace_f0dfF2_obdiag_m9IRRY.trac # 通过 dbms_xplan.enable_opt_trace 收集到的文件
└── obdiag_dbms_xplan_display_cursor.txt # 通过 dbms_xplan.display_cursor 收集到的文件

0 directories, 2 files

3.1. opt_trace 日志解读

理解这部分内容需要具备一定的优化器基础知识,了解 OceanBase 优化器的基础工作原理,包含迭代式的查询改写流程、索引优化、连接枚举、分布式计划优化等。

opt_trace 日志会记录以下信息:

transformer:

  • 每个改写规则报告改写前后的 SQL
  • 每个改写规则发生改写或不发生改写的详细原因(hint 控制还是什么条件不满足)

optimizer:

  • 使用的统计信息
  • 基表路径生成日志(包括过程条件、行数、代价估计信息,skyline 剪枝规则过程)
  • join order 枚举的详细过程
  • top 算子的分配、优化过程

同时日志会在每个模块结束后记录时间和内存开销:

select /*+ PARALLEL(3) */`test`.`game`.`team`,sum(`test`.`game`.`score`) AS `total` from `test`.`game` group by `test`.`game`.`team`
------------------------------------------------------
start prepare mv rewrite info
------------------------------------------------------
table does not have mv, no need to rewrite
-- begin  0  iteration
------------------------------------------------------
start transform rule ObTransformMVRewrite
------------------------------------------------------
transform query block: SEL$1
select /*+ PARALLEL(3) */`test`.`game`.`team`,sum(`test`.`game`.`score`) AS `total` from `test`.`game` group by `test`.`game`.`team`
there is no mv to perform rewrite
transform happened: False
SECTION TIME USAGE: 1338 us
TOTAL TIME USAGE: 1338 us
SECTION MEM USAGE: 240 KB
TOTAL MEM USAGE: 240 KB
  • SECTION TIME USAGE 表示从上个优化步骤结束到当前优化步骤结束所使用的时间。
  • TOTAL TIME USAGE 表示从查询优化开始到当前优化步骤结束所使用的时间。
  • SECTION MEM USAGE 表示从上个优化步骤结束到当前优化步骤结束所使用的租户内存。
  • TOTAL MEM USAGE 表示从查询优化开始到当前优化步骤结束所使用的租户内存。

通过这个信息可以快速定位计划生成耗时、耗内存的优化步骤,并且通过使用 hint 关闭对应优化功能,能够快速解决问题,不再需要粗暴的使用 no_rewrite 关闭整个改写来降低计划生成开销。

3.2. display_cursor 结果

完整的结果:

$cat obdiag_dbms_xplan_display_cursor.txt
obclient> SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
call dbms_xplan.enable_opt_trace();
call dbms_xplan.set_opt_trace_parameter(identifier=>'obdiag_m9IRRY', `level`=>3);
explain select /*+ parallel(3) */ team, sum(score) total from game group by team
call dbms_xplan.disable_opt_trace();

obclient> SELECT DBMS_XPLAN.DISPLAY_CURSOR(273900, 'all', 'xx.xx.xx.xx',  3882, 1) FROM DUAL
+------------------------------------------------------------------------------------------------------------------------+
| DBMS_XPLAN.DISPLAY_CURSOR(273900, 'all', 'xx.xx.xx.xx',  3882, 1)                                                      |
+------------------------------------------------------------------------------------------------------------------------+
| ====================================================================================================================== |
| |ID|OPERATOR                         |NAME    |EST.ROWS|EST.TIME(us)|REAL.ROWS|REAL.TIME(us)|IO TIME(us)|CPU TIME(us)| |
| ---------------------------------------------------------------------------------------------------------------------- |
| |0 |PX COORDINATOR                   |        |6       |11          |3        |26263        |13728      |20599       | |
| |1 |└─EXCHANGE OUT DISTR             |:EX10001|6       |9           |3        |26263        |0          |42          | |
| |2 |  └─HASH GROUP BY                |        |6       |7           |3        |26263        |0          |176         | |
| |3 |    └─EXCHANGE IN DISTR          |        |6       |6           |3        |26263        |6605       |13293       | |
| |4 |      └─EXCHANGE OUT DISTR (HASH)|:EX10000|6       |5           |3        |25206        |0          |13081       | |
| |5 |        └─HASH GROUP BY          |        |6       |3           |3        |25206        |0          |162         | |
| |6 |          └─PX BLOCK ITERATOR    |        |6       |3           |6        |17870        |0          |115         | |
| |7 |            └─TABLE FULL SCAN    |game    |6       |3           |6        |8380         |0          |160         | |
| ====================================================================================================================== |
|                                                                                                                        |
| Outputs & filters:                                                                                                     |
| -------------------------------------                                                                                  |
|   0 - output([INTERNAL_FUNCTION(game.team, T_FUN_SUM(T_FUN_SUM(game.score)))]), filter(nil), rowset=16                 |
|   1 - output([INTERNAL_FUNCTION(game.team, T_FUN_SUM(T_FUN_SUM(game.score)))]), filter(nil), rowset=16                 |
|       dop=3                                                                                                            |
|   2 - output([game.team], [T_FUN_SUM(T_FUN_SUM(game.score))]), filter(nil), rowset=16                                  |
|       group([game.team]), agg_func([T_FUN_SUM(T_FUN_SUM(game.score))])                                                 |
|   3 - output([game.team], [T_FUN_SUM(game.score)]), filter(nil), rowset=16                                             |
|   4 - output([game.team], [T_FUN_SUM(game.score)]), filter(nil), rowset=16                                             |
|       (#keys=1, [game.team]), dop=3                                                                                    |
|   5 - output([game.team], [T_FUN_SUM(game.score)]), filter(nil), rowset=16                                             |
|       group([game.team]), agg_func([T_FUN_SUM(game.score)])                                                            |
|   6 - output([game.team], [game.score]), filter(nil), rowset=16                                                        |
|   7 - output([game.team], [game.score]), filter(nil), rowset=16                                                        |
|       access([game.team], [game.score]), partitions(p[0-2])                                                            |
|       is_index_back=false, is_global_index=false,                                                                      |
|       range_key([game.round]), range(MIN ; MAX)always true                                                             |
|                                                                                                                        |
| Used Hint:                                                                                                             |
| -------------------------------------                                                                                  |
|   /*+                                                                                                                  |
|                                                                                                                        |
|       PARALLEL(3)                                                                                                      |
|   */                                                                                                                   |
| Qb name trace:                                                                                                         |
| -------------------------------------                                                                                  |
|   stmt_id:0, SEL$1                                                                                                     |
|                                                                                                                        |
| Outline Data:                                                                                                          |
| -------------------------------------                                                                                  |
|   /*+                                                                                                                  |
|       BEGIN_OUTLINE_DATA                                                                                               |
|       GBY_PUSHDOWN(@"SEL$1")                                                                                           |
|       USE_HASH_AGGREGATION(@"SEL$1")                                                                                   |
|       PQ_GBY(@"SEL$1"  HASH)                                                                                           |
|       PARALLEL(@"SEL$1" "test"."game"@"SEL$1" 3)                                                                       |
|       FULL(@"SEL$1" "test"."game"@"SEL$1")                                                                             |
|       PARALLEL(3)                                                                                                      |
|       OPTIMIZER_FEATURES_ENABLE('4.3.5.1')                                                                             |
|       END_OUTLINE_DATA                                                                                                 |
|   */                                                                                                                   |
| Optimization Info:                                                                                                     |
| -------------------------------------                                                                                  |
|   game:                                                                                                                |
|       table_rows:6                                                                                                     |
|       physical_range_rows:6                                                                                            |
|       logical_range_rows:6                                                                                             |
|       index_back_rows:0                                                                                                |
|       output_rows:6                                                                                                    |
|       table_dop:3                                                                                                      |
|       dop_method:Global DOP                                                                                            |
|       avaiable_index_name:[game]                                                                                       |
|       stats info:[version=1970-01-01 08:00:00.000000, is_locked=0, is_expired=0]                                       |
|       dynamic sampling level:0                                                                                         |
|       estimation method:[DEFAULT, STORAGE]                                                                             |
|                                                                                                                        |
|   Plan Type:                                                                                                           |
|       DISTRIBUTED                                                                                                      |
|                                                                                                                        |
|   Note:                                                                                                                |
|       Degree of Parallelism is 3 because of hint                                                                       |
|                                                                                                                        |
|                                                                                                                        |
+------------------------------------------------------------------------------------------------------------------------+
(base)
  1. optimizer info 介绍
| Optimization Info:                                                                                                     |
| -------------------------------------                                                                                  |
|   game:                                                                                                                |
|       table_rows:6                                                                                                     |
|       physical_range_rows:6                                                                                            |
|       logical_range_rows:6                                                                                             |
|       index_back_rows:0                                                                                                |
|       output_rows:6                                                                                                    |
|       table_dop:3                                                                                                      |
|       dop_method:Global DOP                                                                                            |
|       avaiable_index_name:[game]                                                                                       |
|       stats info:[version=1970-01-01 08:00:00.000000, is_locked=0, is_expired=0]                                       |
|       dynamic sampling level:0                                                                                         |
|       estimation method:[DEFAULT, STORAGE]                                                                             |
|                                                                                                                        |
|   Plan Type:                                                                                                           |
|       DISTRIBUTED                                                                                                      |
|                                                                                                                        |
|   Note:                                                                                                                |
|       Degree of Parallelism is 3 because of hint
属性名称说明
table_rowsgame 表的原始行数
physical_range_rowsgame 表在索引上需要扫描的物理行数
logical_range_rowsgame 表在索引上需要扫描的逻辑行数
index_back_rowsgame 表需要回表的行数
output_rowsgame 表经过过滤之后的行数
table_dopgame 表扫描的并行度
dop_method决定表扫描并行度的原因,可以是TableDOP(表定义的并行度)、AutoDop(优化器基于代价选择的并行度,需要打开auto dop功能)、global parallel(parallel hint或系统变量设置的并行度)
available_index_namegame 表可用的索引列表
stats infogame 表统计信息版本号
dynamic_sampling_level动态采样(一种优化器优化工具,详细介绍请见官网文档)等级,如果值为0,表示该表没有使用动态采样
estimation_methodgame 表行数估计方式,可以是 DEFAULT(使用的默认统计信息,这种情况行数估计非常不准,需要 DBA 介入优化)、STORAGE(使用存储层实时估行)、STATS(使用统计信息估行)
Plan Type当前计划类型,可以为 LOCAL、REMOTE、DISTRIBUTED
Note生成该计划的一些备注信息,例如:“Degree of Parallelism is 3 because of hint”表示由于当前表的并行度设置为 1,所以当前查询的并行度被设置为 3。
3.2.1. (小妙招)快速定位计划性能差的原因
  1. 找到 CPU TIME 高的 topN 个算子,除去 EXCHANGE IN、EXCHANGE OUT、PX COORD 几个算子,如果有以下算子:
    • TABLE SCAN:查看 Output & filter 信息里面是否有回表 is_index_back=true。如果有,关注一下 optimizer info 里面的 index_back_rows,行数比较多的情况下就需要优化下索引。如果 REAL.ROWS 远高于 EST.ROWS,需要关注一下是否收集过统计信息,或者统计信息过期(通过 optimizer info 的 stats version 字段查看)。都有的话就关注一下是否有复杂的过滤条件,例如 case when、like 等等。这种情况下可以通过手动开启动态采样 /+dynamic_sampling(1)/ 来提高估行的准确度。如果这个算子在 Nested Loop Join、SubPlan Filter 算子的右侧,说明是 rescan 太多导致的开销大。
    • Nested Loop Join:首先看看左侧算子的行数估计是否正确,如果行数偏差较大,则关注下统计信息问题,或者开启动态采样 /+dynamic_sampling(1)/ 来提高估行的准确度,如果这些都没用就使用 /+use_hash(xxx)/ 来绑定其他计划。如果行数估计正常,性能还是差,需要关注一下 Output & filter 信息,是否使用了 batch_join。
    • SubPlan Filter:首先看看左侧算子的行数估计是否正确,如果行数偏差较大,则关注下统计信息问题,或者开启动态采样 /+dynamic_sampling(1)/ 来提高估行的准确度。如果行数估计正常,性能还是差,需要关注一下 Output & filter 信息,是否使用了 batch。如果以上方式都对,需要在 SQL 里面找到对应的子查询,看看能不能改写优化 /+unnest/。

对于其他比较慢的算子,关注一下估行及统计信息的状态,收集统计信息或开启动态采样之后依然没有改善,说明就是数据量比较大,需要开启并行执行 /+parallel(xxx)/,对于 INSERT、UPDATE、DELETE、MERGE 类型算子,需要额外开启 PDML 来优化 /+parallel(xxx) enable_parallel_dml/。

2. 如果注意到 HASH DISTINCT、SORT、HASH GROUP BY、HASH JOIN 等算子有 IO TIME,说明这些算子有落盘的情况,可以适当调整 sql_work_area_size 参数。

4. 写在最后

上面是给大家分享的通过obdiag 来获取DBMS_XPLAN 的信息,非常有助于帮助大家排查SQL性能问题,当然了,大部分情况下排查SQL问题需要的表结构、执行计划等信息可以通过我的另一篇博客:

OceanBase诊断调优 (二十二)—— 并行SQL/慢SQL 问题该如何高效收集诊断信息 这里面拿到的是一份html的报告,基本上足够排查了。

5. 附录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值