---
title: "PL/SQL 性能问题定位最佳实践 - OceanBase 最佳实践 | OceanBase 文档中心"
description: PL/SQL 性能问题定位最佳实践 在 AP（Analytical Processing）场景下，存储过程的稳定性和性能是业务能否落地的关键。在实际的业务实践中，业务更关注存储过程的性能，比如每日的跑批是否能在合理的时间内完成结算是业务更关注的指标。 在 PL/SQL 开发过程中，性能分析是确保系统高效运行的关键环节…
image: https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*OSPzQ6GUQF4AAAAAQHAAAAgAeiGDAQ/original
---
切换语言

- 中文站 - 简体中文
- International - English
- 日本站 - 日本語

# PL/SQL 性能问题定位最佳实践

更新时间：2026-07-28

[编辑](https://github.com/oceanbase/best-practices-doc/edit/master/zh-CN/600.develop/1200.pl-sql-perf-analysis.md) 适用产品： OceanBase 数据库 适用场景： 性能调优  

在 AP（Analytical Processing）场景下，存储过程的稳定性和性能是业务能否落地的关键。在实际的业务实践中，业务更关注存储过程的性能，比如每日的跑批是否能在合理的时间内完成结算是业务更关注的指标。

在 PL/SQL 开发过程中，性能分析是确保系统高效运行的关键环节。在 OceanBase 数据库 V4.2.3 之前，PL/SQL 性能问题定位手段有限，主要依靠 `GV$OB_SQL_AUDIT` 视图分析 PL 和内部 SQL 的执行时间差异，但是无法获取语法块（WHILE 和 FOR 等）和语句级别的详细执行时间。在跑批场景中，循环语句频繁执行相同 SQL 语句，当触发 SQL Audit 视图淘汰机制时，无法记录完整的执行时间信息，影响性能问题定位。因此，在 OceanBase 数据库 V4.2.3 版本引入了 DBMS_PROFILER，提供行级耗时性能定位功能，兼容 Oracle 的 PL 行级别的性能分析工具，可以抓取一个 PL 请求中行级别的执行热点信息。

## SQL Audit 适用场景

- **全面 SQL 监控**：能够记录所有 SQL 语句的执行信息，包括执行时间、等待事件等，适用于需要全面监控和统计的场景
 - **热点 SQL 分析**：通过 SQL Audit 可以记录每次 SQL 执行的状态信息，包括重试次数（retry_cnt）、排队时间（queue_time）、获取执行计划时间（get_plan_time）、执行时间（execute_time）等
 - **与 OCP 集成**：结合 OceanBase 云平台（OCP）直观地发现热点 SQL，进行针对性优化
 - **宏观性能分析**：适用于需要从整体角度分析 PL/SQL 和内部 SQL 执行效率的场景，SQL 统计时间上更完备，包括是否存在 RPC、网络等待、队列等待等信息

### SQL Audit 的局限性

- **数据丢失风险**：受限于 SQL Audit 缓存大小，较早的审计记录可能被提前刷出，导致统计不完备
 - **分析复杂度**：当 PL 中触发的 SQL 较多时，对单个 PL 进行性能分析的计算量较大，可能影响分析效率
 - **无法统计非 SQL 执行时间**：无法统计 PL 中非 SQL 执行的时间信息，可能遗漏部分性能瓶颈

## DBMS_PROFILER 适用场景

- **行级性能分析**：需要对 PL/SQL 代码进行行级别性能分析的场景，提供 PL/SQL 代码的行级别执行信息，能够精确定位性能瓶颈
 - **精确问题定位**：可以捕获 PL/SQL 程序中每一行代码的执行次数、总耗时、最小耗时和最大耗时等信息，便于深入分析
 - **性能瓶颈识别**：从而定位性能瓶颈，优化代码逻辑，能够准确识别代码中的性能热点，为优化提供明确方向
 - **代码级调优**：适用于需要深入分析 PL/SQL 内部执行逻辑的场景

### DBMS_PROFILER 的局限性

- **编译开销**：由于需要对 PL/SQL 代码进行插桩，可能引入额外的编译开销，影响系统性能
 - **性能影响**：统计过程中需要在每行指令上插桩，可能对整体执行性能产生影响，预期会有约 2% 的性能开销
 - **编译复用问题**：因为 DBMS_PROFILER 需要对编译结果插桩，因此不能复用已有的编译。当前的设计，DBMS_PROFILER 引起的存储过程编译结果只能在当前会话复用（在 V4.4.2 版本会解决编译结果复用的问题）
 - **并发场景限制**：在并发场景下的性能数据收集较为复杂，需要通过系统触发器等方式进行管理

## 典型业务场景

### 场景一：存储过程性能瓶颈定位

- **业务特点**：高频执行的存储过程出现性能瓶颈，需要精确定位耗时代码段
 - **行业应用**：金融跑批处理、电商大促场景、保险理赔计算
 - **技术挑战**：循环内细粒度执行时间分析、热点 SQL 识别、编译问题诊断

### 场景二：Oracle 迁移后性能对比

- **业务特点**：从 Oracle 迁移到 OceanBase 后需要验证性能是否达标
 - **行业应用**：银行核心系统、ERP 系统、数据仓库等
 - **技术挑战**：迁移前后代码行级执行时间对比、性能回退分析

### 场景三：复杂业务逻辑性能优化

- **业务特点**：包含 UDF 调用、批量处理、多重循环的复杂存储过程
 - **行业应用**：数据分析平台、ETL 作业、实时计算场景
 - **技术挑战**：多层次性能问题诊断、优化策略制定

## 使用 SQL Audit 分析 PL 性能

SQL Audit 提供了多个 PL 相关的重要字段用于性能分析：

| 字段名 | 数据类型 | 说明 |
| --- | --- | --- |
| REQUEST_TYPE | NUMBER(38) | 请求类型，request_type = 11 代表来自 PL 的 SQL 语句 |
| PL_TRACE_ID | VARCHAR2(128) | PL 跟踪 ID，用于关联 PL 和其内部 SQL 语句 |
| PLSQL_EXEC_TIME | NUMBER(38) | 纯 PL 执行时间（扣除 SQL 执行时间） |
| PLSQL_COMPILE_TIME | NUMBER(38) | PL 编译时间 |

### REQUEST_TYPE

您可以通过 `REQUEST_TYPE` 判断当前请求是否来自 PL。`REQUEST_TYPE = 11` 代表来自 PL 的 SQL 语句。当您查询 `GV$OB_SQL_AUDIT` 视图时，可以通过 `REQUEST_TYPE = 11` 来筛选出所有来自 PL/SQL 的 SQL 语句。

### PL_TRACE_ID

从 V4.2.2 版本引入，`PL_TRACE_ID` 将 PL 与 PL 中 SQL 语句的执行切分开。一个请求中 PL 的执行是一个统一的顶层 `PL_TRACE_ID`，PL 中的每个 SQL 则有独立的 `TRACE_ID`。当您查询 `GV$OB_SQL_AUDIT` 视图时，可以通过 `PL_TRACE_ID` 来关联 PL 请求中所有 SQL 语句。

在 V4.2.2 之后，当您查询 `GV$OB_SQL_AUDIT` 视图时，需要通过 `PL_TRACE_ID` 来关联 PL 请求中所有 SQL 语句。示例如下：

```sql
-- 示例：查看 PL 执行详情
CREATE TABLE t (id INT, name VARCHAR(50));

CREATE OR REPLACE PROCEDURE p IS
    x INT;
BEGIN
    SELECT COUNT(*) INTO x FROM t;
END;
/

CALL p();

SELECT query_sql, trace_id, pl_trace_id, plsql_exec_time, plsql_compile_time, request_type
FROM gv$ob_sql_audit
WHERE pl_trace_id = last_trace_id();

```

输出示例：

```sql
+----------------------------------------------+-----------------------------------+-----------------------------------+-----------------+--------------------+--------------+
| QUERY_SQL                                    | TRACE_ID                          | PL_TRACE_ID                       | PLSQL_EXEC_TIME | PLSQL_COMPILE_TIME | REQUEST_TYPE |
+----------------------------------------------+-----------------------------------+-----------------------------------+-----------------+--------------------+--------------+
| select count(*) AS "COUNT(*)" from "SYS"."T" | YB42AC1E87C6-00063EDCF3A608A6-0-0 | YB42AC1E87C6-00063ECE9A6608E3-0-0 |               0 |                  0 |           11 |
| CALL p()                                     | YB42AC1E87C6-00063ECE9A6608E3-0-0 | YB42AC1E87C6-00063ECE9A6608E3-0-0 |             297 |              14484 |            2 |
+----------------------------------------------+-----------------------------------+-----------------------------------+-----------------+--------------------+--------------+

```

### PLSQL_EXEC_TIME

从 V4.2.2 版本引入，`PLSQL_EXEC_TIME` 记录了当前请求中纯 PL 执行的时间，即扣除 SQL 执行时间剩余的时间。您可以通过该字段判断当前的 PL 请求本身是否为性能瓶颈。

### PLSQL_COMPILE_TIME

记录当前请求中因为 PL 编译所花费的时间。在一个稳定系统中，频繁编译会导致严重的性能问题，因此如果系统中该时间频繁出现，则需要关注是否有编译问题。

## 使用 DBMS_PROFILER 分析 PL性能

为了能更细节地分析 PL 执行过程的详细耗时，在 V4.2.3 版本引入了 DBMS_PROFILER 包。DBMS_PROFILER 是兼容 Oracle 的 PL 行级别的性能分析工具，可以抓取一个 PL 请求中行级别的执行热点信息。

#### 说明

由于当前 USER_SOURCE 表中存储的过程源码信息只有一行，在执行完 DBMS_PROFILER 分析后，数据与存储过程源码关联需要一个代理表。在 V4.4.2 版本，将会提供 USER_SOURCE 表，该表提供按照行拆分的 USER_SOURCE 视图，DBMS_PROFILER 后的数据可以与 USER_SOURCE 关联到具体的行。

### DBMS_PROFILER 使用示例

```sql
-- 启动性能分析
CALL DBMS_PROFILER.start_profiler(run_comment => 'test' || SYSDATE);

CREATE TABLE t (id INT, name VARCHAR(50));

-- 创建测试存储过程
CREATE OR REPLACE PROCEDURE p2 IS
    x INT;
BEGIN
    FOR idx IN 1..10000 LOOP
    BEGIN
        x := idx + 1;
        INSERT INTO t VALUES(x, 'test' || x);
    END;
    END LOOP;
END;
/

-- 执行存储过程
CALL p2();

-- 停止性能分析
CALL DBMS_PROFILER.stop_profiler();

```

```sql
-- 查看性能分析结果
SELECT * FROM plsql_profiler_runs;

```

输出示例：

```text
+-------+-------------+-----------+-----------+---------------+----------------+-----------------+--------------+--------+
| RUNID | RELATED_RUN | RUN_OWNER | RUN_DATE  | RUN_COMMENT   | RUN_TOTAL_TIME | RUN_SYSTEM_INFO | RUN_COMMENT1 | SPARE1 |
+-------+-------------+-----------+-----------+---------------+----------------+-----------------+--------------+--------+
|     1 |        NULL | SYS       | 23-SEP-25 | test23-SEP-25 |   667000000000 | NULL            | NULL         | NULL   |
+-------+-------------+-----------+-----------+---------------+----------------+-----------------+--------------+--------+

```

```sql
-- 查看执行单元
SELECT * FROM plsql_profiler_units WHERE runid=1;

```

输出示例：

```text
+-------+-------------+--------------+------------+---------------+----------------+------------+--------+--------+
| RUNID | UNIT_NUMBER | UNIT_TYPE    | UNIT_OWNER | UNIT_NAME     | UNIT_TIMESTAMP | TOTAL_TIME | SPARE1 | SPARE2 |
+-------+-------------+--------------+------------+---------------+----------------+------------+--------+--------+
|     1 |      311114 | PACKAGE BODY | SYS        | DBMS_PROFILER | 15-SEP-25      |          0 |   NULL |   NULL |
|     1 |      500002 | PROCEDURE    | SYS        | P             | 22-SEP-25      |          0 |   NULL |   NULL |
|     1 |      500019 | PROCEDURE    | SYS        | P2            | 23-SEP-25      |          0 |   NULL |   NULL |
+-------+-------------+--------------+------------+---------------+----------------+------------+--------+--------+

```

```sql
-- 查看行级性能数据
SELECT * FROM plsql_profiler_data WHERE runid=1;

```

输出示例：

```text
+-------+-------------+-------+-------------+------------+----------+----------+--------+--------+--------+--------+
| RUNID | UNIT_NUMBER | LINE# | TOTAL_OCCUR | TOTAL_TIME | MIN_TIME | MAX_TIME | SPARE1 | SPARE2 | SPARE3 | SPARE4 |
+-------+-------------+-------+-------------+------------+----------+----------+--------+--------+--------+--------+
|     1 |      311095 |     1 |           2 |       7945 |     2016 |     5929 |   NULL |   NULL |   NULL |   NULL |
|     1 |      311095 |    18 |           2 |        611 |      278 |      333 |   NULL |   NULL |   NULL |   NULL |
|     1 |      311095 |    21 |           2 |      82814 |    26726 |    56088 |   NULL |   NULL |   NULL |   NULL |
|     ... |        ... |   ... |         ... |        ... |      ... |      ... |    ... |    ... |    ... |    ... |
+-------+-------------+-------+-------------+------------+----------+----------+--------+--------+--------+--------+

```

## 并发场景下的性能数据收集

有些业务场景需要统计一段时间内的存储过程耗时开销，这里推荐使用 Oracle 兼容的方式。通过系统触发器的方式，在每次会话登录的 LogOn 触发器上 StartProfiler，在 LogOff 触发器上 StopProfiler。

#### 说明

系统触发器在 V4.2.5.bp1 开始支持。

```sql
-- 创建登录触发器
CREATE OR REPLACE TRIGGER after_logon_trg
AFTER LOGON ON DATABASE
WHEN (ora_login_user() IN ('TEST'))
BEGIN
    dbms_profiler.start_profiler();
END;
/

-- 创建登出触发器
CREATE OR REPLACE TRIGGER before_logoff_trg
BEFORE LOGOFF ON DATABASE
WHEN (ora_login_user() IN ('TEST'))
BEGIN
    dbms_profiler.stop_profiler();
END;
/

```

## 组合使用策略

建议采用分层分析策略：

1. **第一步**：使用 SQL Audit 进行初步分析，识别性能瓶颈的 PL/SQL 对象
 2. **第二步**：对识别出的问题对象使用 DBMS_PROFILER 进行深度分析
 3. **第三步**：基于分析结果进行代码优化
 4. **第四步**：使用 SQL Audit 验证优化效果

## 总结

在 OceanBase 数据库中，SQL Audit 和 DBMS_PROFILER 各有优势，适用于不同的性能分析需求：

- **SQL Audit** 更适合全局 SQL 执行情况的监控和统计，提供宏观的性能分析视角
 - **DBMS_PROFILER** 更适合对 PL/SQL 代码进行深入的行级别性能分析，提供微观的性能分析能力

在实际应用中，可根据具体需求选择合适的工具，或结合使用，以获得更全面的性能分析结果。通过系统化的性能分析方法，能够有效识别和解决 PL/SQL 性能问题，提升系统整体运行效率。

## 参考文档

- [DBMS_PROFILER 概述](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000002016648)
 - [DBMS_PROFILER 存储过程调优工具](https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000002356139)

 上一篇 ![有帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*y6ocSqN8cqsAAAAAAAAAAAAAARQnAQ)![无帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*BG9IQJyLHF8AAAAAAAAAAAAAARQnAQ)![反馈](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*eTWdQKCRKHwAAAAAAAAAAAAAARQnAQ)[AI](https://www.oceanbase.com/obi) 咨询热线
