网站地图

专栏 说明 入口
Oracle OCM 级别实战:RAC、DataGuard、性能调优、故障恢复 Oracle 知识地图
MySQL 主从复制、MGR、慢查询优化 MySQL 知识地图
PostgreSQL 高可用、备份恢复、迁移 PostgreSQL 知识地图
信创/国产化 达梦、OceanBase、GaussDB 信创知识地图
综合运维 中间件、Linux、监控、自动化工具 综合运维知识地图
运维夜话 运维避坑、职业规划、技术管理思考与行业趋势 运维夜话

联系方式

  • 如有数据库运维需求,可通过以下方式联系:

关于我

Oracle OCM,深耕半导体数据库运维多年。

擅长 Oracle、MySQL、PostgreSQL 等主流数据库的日常运维、性能调优和故障处理。

提供专业 Oracle/MySQL/PostgreSQL 数据恢复、性能优化、迁移升级、紧急救援等服务。

本文是 Oracle 新特性系列的第 28 篇(收官篇),聚焦 Oracle 23ai 最核心的两个 AI 特性:AI Vector Search 与 JSON Relational Duality View。

一、问题背景

随着 LLM(大语言模型)和 RAG(Retrieval Augmented Generation)应用的爆发式增长,数据库不再只是存储结构化数据的仓库,它还需要具备向量检索语义搜索的能力。传统做法是将向量数据存入专用的向量数据库(如 Milvus、Pinecone),但这意味着架构中多了一个组件,数据同步、一致性、运维复杂度都会随之增加。

Oracle 在 23ai 版本中正式将自己定位为 AI Database,核心思路是:让关系型数据库原生支持向量操作,无需引入外部向量库,就能在 SQL 层面完成向量存储、索引和相似度检索。同时,23ai 推出了 JSON Relational Duality View,彻底打通关系模型与文档模型之间的壁垒。

对于 DBA 而言,这些特性不是”锦上添花”,而是必须掌握的技能栈

  • 应用团队越来越多地要求在数据库层做向量检索,而不是维护额外的向量库
  • JSON Duality View 让开发者用 JSON 接口操作关系表,DBA 需要理解其背后的机制才能做好性能调优
  • 23ai 的 AI 特性已经进入生产环境,早一步掌握就意味着早一步建立技术壁垒
阅读全文 »

一、问题背景

1.1 数据库服务化的需求

在现代企业架构中,数据库往往不是孤立存在的——它需要为前端应用、移动端、第三方系统以及微服务架构提供数据支撑。随着前后端分离和微服务架构的普及,将数据库中的数据以标准化的 RESTful API 形式对外暴露,已经成为企业数字化转型中的刚性需求。

传统做法是在数据库之上搭建一层中间件(Java Spring Boot、Node.js 等),通过 JDBC/ODBC 连接数据库,再对外暴露 REST API。这种方案虽然可行,但在实际落地过程中存在几个明显痛点:

  • 开发成本高:每个数据实体都需要编写 Controller、Service、DAO 层代码,一个中等规模的系统可能涉及数十甚至上百个实体类,代码量巨大
  • 维护负担重:数据库 Schema 变更时,中间件代码需要同步修改,任何一个字段的增删改都可能引发连锁变更
  • 延迟增加:多了一层网络跳转,增加了响应时间和故障点,每次请求都要经过应用服务器的序列化和反序列化
  • 技能栈要求:DBA 团队通常不擅长 Java/Node.js 开发,而开发团队又不熟悉数据库内部逻辑,沟通成本高昂
  • 部署复杂:需要额外的应用服务器环境,增加了运维复杂度和基础设施成本

1.2 ORDS 的价值

Oracle REST Data Services(ORDS)提供了一种优雅的替代方案:零代码将 SQL 查询、PL/SQL 过程直接暴露为 RESTful API。DBA 只需要写 SQL 或 PL/SQL,ORDS 自动处理 HTTP 协议处理、JSON 序列化与反序列化、连接池管理、安全认证授权等所有 Web 层逻辑。这意味着 DBA 可以专注于数据逻辑本身,而无需学习和维护复杂的 Web 开发框架。

核心价值体现在:

维度 传统中间件方案 ORDS 方案
开发效率 需编写全栈代码 SQL/PL/SQL 即 API
部署复杂度 应用服务器 + 数据库 ORDS + 数据库
延迟 多一跳(应用→数据库) 直连数据库
维护成本 Schema 变更需改代码 AutoREST 自动适配
技能要求 Java/JS + SQL SQL/PL/SQL 即可

1.3 与传统中间件方案的对比

ORDS 并非要取代所有中间件,它最适合的场景是数据密集型 CRUD 服务数据库逻辑封装。对于简单的数据增删改查、报表查询、数据导出等场景,ORDS 能够以最低的成本实现 API 暴露。对于复杂的业务编排、多数据源聚合、异步消息处理等场景,仍然需要传统中间件配合。两者可以共存——ORDS 负责数据库直接暴露的 API,中间件负责复杂业务逻辑,各司其职。

从实际项目经验来看,一个典型的微服务系统中,大约有 60%70% 的 API 属于简单的数据 CRUD 操作,这些完全可以用 ORDS 来实现。剩下的 30%40% 涉及复杂业务逻辑的 API,才需要传统的中间件方案。这种分工模式可以显著降低整体开发成本和系统复杂度。


二、理论分析

2.1 ORDS 架构

ORDS 本质上是一个运行在 JVM 上的 Java Web 应用,它在 HTTP 前端和 Oracle 数据库之间充当桥梁:

1
2
3
4
5
6
客户端 ──HTTP/HTTPS──▶ ORDS (Jetty/Tomcat/WLS) ──JDBC──▶ Oracle Database

├── 连接池管理 (UCP/HikariCP)
├── 请求路由 (Module → Template → Handler)
├── 认证授权 (OAuth2/Basic Auth)
└── 响应格式化 (JSON/XML/CSV)

连接池机制:ORDS 使用 Universal Connection Pool (UCP) 管理数据库连接。每个数据库连接配置(称为 database connection)维护一个独立的连接池,支持连接复用、超时回收、健康检查等特性。连接池的大小可以通过配置文件灵活调整,初始连接数、最大连接数、空闲超时时间等参数都可以根据实际负载进行调优。这是 ORDS 性能的关键所在——避免了每次请求都建立新连接的开销,显著降低了数据库的连接管理压力。在高并发场景下,合理的连接池配置能够将 API 响应时间降低数倍。

与 APEX 的关系:ORDS 的前身就是 APEX Listener,它天然支持 Oracle APEX 应用的运行。APEX 的页面渲染、动态操作等功能都是通过 ORDS 来处理 HTTP 请求的。但 ORDS 的功能远不止于 APEX——它可以独立于 APEX 使用,纯粹作为 REST API 服务。在没有安装 APEX 的数据库上,ORDS 同样可以正常工作。实际上,很多企业使用 ORDS 的场景完全不涉及 APEX,只是利用它来暴露数据库服务。

部署架构灵活性:ORDS 支持多种部署方式。在开发和测试阶段,可以使用内置的 Jetty 服务器以独立模式(Standalone)运行,启动简单、无需额外依赖。在生产环境中,推荐将 ORDS 部署到 Tomcat 或 WebLogic Server 中,配合 Nginx 反向代理实现负载均衡和 SSL 卸载。此外,ORDS 还支持容器化部署,可以打包为 Docker 镜像运行在 Kubernetes 集群中。

2.2 REST API 设计

ORDS 的 REST API 设计遵循层级结构:

1
2
3
4
Base Path (e.g., /ords/hr/)
└── Module (e.g., employees/)
└── Template (e.g., {id}/)
└── Handler (e.g., GET → SELECT ... WHERE id = :id)
  • Module:逻辑分组,类似 API 的命名空间
  • Template:URL 路径模板,支持 {param} 占位符
  • Handler:绑定具体的 HTTP 方法(GET/POST/PUT/DELETE)和 SQL/PL/SQL

参数绑定支持三种来源:

  • URI 模板参数{id}:id,用于标识资源的唯一性,例如 /employees/{empno} 中的 empno
  • Query String 参数?status=ACTIVE:status,用于过滤、排序和分页控制
  • Request Body 参数(POST/PUT 的 JSON body)→ :field_name,用于创建和更新操作的数据传递

响应格式默认为 JSON,也支持 XML 和 CSV。JSON 响应遵循 Oracle 的标准格式,包含 items 数组、分页元数据(hasMorelimitoffsetcount)以及相关链接(links)等。这种标准化的响应格式使得前端开发者能够快速对接,无需额外的格式转换逻辑。对于列表查询,ORDS 自动实现分页功能,开发者只需在 Handler 中设置 items_per_page 参数即可。

2.3 AutoREST

AutoREST 是 ORDS 最强大也最具革命性的特性之一。它彻底改变了数据库暴露 API 的方式——只需一条 PL/SQL 命令,就能将任意表或视图自动暴露为完整的 RESTful API,无需编写任何 SQL Handler 代码。

  • 表/视图的 AutoREST:启用后,ORDS 会自动为该表生成五个标准 REST 端点——GET 列表(支持分页、过滤、排序)、GET 单条记录(按主键查询)、POST 创建记录、PUT 更新记录、DELETE 删除记录。这些端点完全遵循 RESTful 规范,返回标准化的 JSON 响应。更强大的是,AutoREST 支持通过 Query String 参数进行灵活的过滤和排序,例如 ?q={"salary":{"$gt":5000}} 可以实现类似 MongoDB 的查询语法
  • PL/SQL 过程暴露:除了表和视图,ORDS 还可以将存储过程、函数直接映射为 POST 端点。参数自动从 JSON Request Body 中提取,输出参数自动包含在 JSON 响应中。这使得复杂的数据库逻辑也能以 API 的形式对外提供服务
  • 安全控制:AutoREST 默认不暴露任何对象,必须显式授权才能访问。支持 Schema 级别、对象级别、甚至列级别的精细权限控制。可以通过 OAuth2 客户端角色来管理访问权限,确保只有经过授权的应用才能访问特定的数据资源。这种”默认拒绝”的安全模型,使得 AutoREST 在生产环境中使用也非常安全

三、实战操作

3.1 ORDS 安装配置

3.1.1 环境准备

ORDS 23.x 要求 JDK 17+ 和 Oracle Database 19c+。以下基于 ORDS 23.4 + Oracle 19c 环境。

1
2
3
4
5
6
7
8
9
10
# 安装 JDK 17
sudo yum install -y java-17-openjdk java-17-openjdk-devel

# 验证 Java 版本
java -version
# openjdk version "17.0.x" ...

# 创建 ORDS 安装目录
sudo mkdir -p /opt/oracle/ords
sudo chown oracle:oinstall /opt/oracle/ords

3.1.2 ORDS 安装

1
2
3
4
5
6
# 下载 ORDS(从 OTN 或 edelivery)
cd /opt/oracle/ords
unzip ords-23.4.0.24.1153.zip -d /opt/oracle/ords/

# 创建配置目录
mkdir -p /opt/oracle/ords/config/ords

3.1.3 数据库连接配置

创建 databases/default/connection.xml 配置文件(或使用安装命令交互式配置):

1
2
3
4
5
6
7
8
9
10
# 交互式安装配置
cd /opt/oracle/ords
java -jar ords.war install advanced

# 安装过程中需要指定:
# - 数据库连接类型:Basic (SID) 或 Service Name
# - 主机名、端口(默认1521)
# - 数据库 SID 或 Service Name
# - ORDS 公共用户密码
# - 管理员连接信息

也可以使用命令行非交互式配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 配置数据库连接
java -jar ords.war set-properties <<EOF
db.hostname=dbserver.example.com
db.port=1521
db.servicename=ORCLPDB1
db.username=ORDS_PUBLIC_USER
db.password=<secure_password>
plsql.gateway.add=true
rest.services.apex.add=true
schema.tablespace.default=SYSAUX
schema.tablespace.temp=TEMP
standalone.mode=false
EOF

3.1.4 安装 ORDS Schema

1
2
3
4
5
6
7
# 以 SYSDBA 身份在数据库中安装 ORDS 元数据
# 这会创建 ORDS 相关的 schema 和对象
java -jar ords.war install simple \
--database ORCLPDB1 \
--host dbserver.example.com \
--port 1521 \
--passwordFile /opt/oracle/ords/admin_password.txt

3.1.5 配置独立模式(Standalone)或 Tomcat 部署

独立模式(适合开发/测试):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 启动 ORDS 独立模式
java -jar ords.war standalone \
--port 8080 \
--host 0.0.0.0

# 生产环境建议使用 systemd 管理
cat > /etc/systemd/system/ords.service <<'EOF'
[Unit]
Description=Oracle REST Data Services
After=network.target

[Service]
Type=simple
User=oracle
WorkingDirectory=/opt/oracle/ords
ExecStart=/usr/bin/java -Dconfig.url=/opt/oracle/ords/config -jar /opt/oracle/ords/ords.war standalone --port 8080
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable ords
sudo systemctl start ords

Tomcat 部署(适合生产):

1
2
3
4
5
# 将 ORDS 部署到 Tomcat
cp /opt/oracle/ords/ords.war $CATALINA_HOME/webapps/ords.war
# 重启 Tomcat
$CATALINA_HOME/bin/shutdown.sh
$CATALINA_HOME/bin/startup.sh

3.1.6 HTTPS 配置

生产环境必须启用 HTTPS。推荐通过 Nginx 反向代理实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 443 ssl http2;
server_name api.example.com;

ssl_certificate /etc/ssl/certs/api.example.com.crt;
ssl_certificate_key /etc/ssl/private/api.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;

location /ords/ {
proxy_pass http://127.0.0.1:8080/ords/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

3.2 创建 REST 服务

3.2.1 手动创建 Module/Template/Handler

通过 SQL Developer 或直接在数据库中执行 PL/SQL 来创建 REST 服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
-- 1. 启用 ORDS schema(以 HR 用户为例)
BEGIN
ORDS.ENABLE_SCHEMA(
p_enabled => TRUE,
p_schema => 'HR',
p_url_mapping_type => 'BASE_PATH',
p_url_mapping_pattern => 'hr',
p_auto_rest_auth => FALSE
);
COMMIT;
END;
/

-- 2. 创建 Module
BEGIN
ORDS.DEFINE_MODULE(
p_module_name => 'employees.api',
p_base_path => '/emp/',
p_items_per_page => 25,
p_status => 'PUBLISHED',
p_comments => '员工信息 REST API'
);
COMMIT;
END;
/

-- 3. 创建 Template(URL 路径模板)
BEGIN
ORDS.DEFINE_TEMPLATE(
p_module_name => 'employees.api',
p_pattern => ':empno',
p_priority => 0,
p_etag_type => 'HASH',
p_comments => '按员工编号查询'
);
COMMIT;
END;
/

-- 4. 创建 Handler(GET 方法 - 查询单个员工)
BEGIN
ORDS.DEFINE_HANDLER(
p_module_name => 'employees.api',
p_pattern => ':empno',
p_method => 'GET',
p_source_type => 'json/query',
p_items_per_page => 0,
p_mimes_allowed => '',
p_comments => '',
p_source => 'SELECT e.employee_id, e.first_name, e.last_name,
e.email, e.salary, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.employee_id = :empno'
);
COMMIT;
END;
/

-- 5. 创建 Handler(GET 方法 - 查询员工列表)
BEGIN
ORDS.DEFINE_TEMPLATE(
p_module_name => 'employees.api',
p_pattern => 'list',
p_comments => '员工列表查询'
);
COMMIT;
END;
/

BEGIN
ORDS.DEFINE_HANDLER(
p_module_name => 'employees.api',
p_pattern => 'list',
p_method => 'GET',
p_source_type => 'json/query',
p_items_per_page => 25,
p_source => 'SELECT e.employee_id, e.first_name, e.last_name,
e.salary, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
ORDER BY e.employee_id'
);
COMMIT;
END;
/

创建完成后,可以通过以下 URL 访问:

1
2
GET https://api.example.com/ords/hr/emp/list
GET https://api.example.com/ords/hr/emp/100

3.2.2 AutoREST 启用

AutoREST 可以在 Schema 级别一键启用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
-- 启用 Schema 的 REST 服务
BEGIN
ORDS.ENABLE_SCHEMA(
p_enabled => TRUE,
p_schema => 'HR',
p_url_mapping_type => 'BASE_PATH',
p_url_mapping_pattern => 'hr'
);
COMMIT;
END;
/

-- 启用表的 AutoREST(以 EMPLOYEES 表为例)
BEGIN
ORDS.ENABLE_OBJECT(
p_enabled => TRUE,
p_schema => 'HR',
p_object => 'EMPLOYEES',
p_object_type => 'TABLE',
p_object_alias => 'employees'
);
COMMIT;
END;
/

-- 启用视图的 AutoREST
BEGIN
ORDS.ENABLE_OBJECT(
p_enabled => TRUE,
p_schema => 'HR',
p_object => 'EMP_DETAILS_VIEW',
p_object_type => 'VIEW',
p_object_alias => 'emp_details'
);
COMMIT;
END;
/

启用后,以下端点自动可用:

1
2
3
4
5
GET    /ords/hr/employees/          # 列表(支持分页)
GET /ords/hr/employees/:id # 单条记录
POST /ords/hr/employees/ # 创建记录
PUT /ords/hr/employees/:id # 更新记录
DELETE /ords/hr/employees/:id # 删除记录

3.2.3 参数化查询

在 Handler 中支持多种参数绑定方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 带分页和过滤的查询
BEGIN
ORDS.DEFINE_HANDLER(
p_module_name => 'employees.api',
p_pattern => 'search',
p_method => 'GET',
p_source_type => 'json/query',
p_source => 'SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE (:min_salary IS NULL OR salary >= :min_salary)
AND (:dept_id IS NULL OR department_id = :dept_id)
ORDER BY salary DESC'
);
COMMIT;
END;
/

调用示例:

1
2
# 查询薪资大于 5000 且部门 ID 为 50 的员工
curl -X GET "https://api.example.com/ords/hr/emp/search?min_salary=5000&dept_id=50"

3.3 安全配置

3.3.1 OAuth2 认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- 创建 OAuth2 客户端
BEGIN
OAUTH.CREATE_CLIENT(
p_name => 'Employee App',
p_grant_type => 'client_credentials',
p_owner => 'HR Department',
p_description => '员工管理系统 API 客户端',
p_support_email => 'dba@example.com',
p_privilege_names => ''
);
COMMIT;
END;
/

-- 授予客户端角色
BEGIN
OAUTH.GRANT_CLIENT_ROLE(
p_client_name => 'Employee App',
p_role_name => 'oracle.dbtools.autorest.any.schema'
);
COMMIT;
END;
/

-- 获取 Client ID 和 Secret
-- 查询: SELECT * FROM OAUTH_APPROVALS;
1
2
3
4
5
6
7
8
9
10
# 获取 Access Token
curl -X POST "https://api.example.com/ords/hr/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=<client_id>" \
-d "client_secret=<client_secret>"

# 使用 Token 调用 API
curl -X GET "https://api.example.com/ords/hr/employees/" \
-H "Authorization: Bearer <access_token>"

3.3.2 角色授权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-- 创建自定义角色
BEGIN
ORDS.CREATE_ROLE(p_role_name => 'hr_api_reader');
COMMIT;
END;
/

-- 将角色授予 OAuth2 客户端
BEGIN
OAUTH.GRANT_CLIENT_ROLE(
p_client_name => 'Employee App',
p_role_name => 'hr_api_reader'
);
COMMIT;
END;
/

-- 在 Module 级别要求角色
BEGIN
ORDS.DEFINE_PRIVILEGE(
p_privilege_name => 'hr.read.employees',
p_roles => 'hr_api_reader',
p_patterns => '/emp/*',
p_modules => 'employees.api',
p_label => 'HR Employees Read Access',
p_description => '允许读取员工信息',
p_comments => ''
);
COMMIT;
END;
/

-- 启用权限
BEGIN
ORDS.ENABLE_PRIVILEGE(
p_privilege_name => 'hr.read.employees',
p_enabled => TRUE
);
COMMIT;
END;
/

3.3.3 速率限制

通过 ORDS 配置文件设置全局速率限制:

1
2
3
# 在 defaults.xml 中配置
<entry key="security.http.maxRequests">100</entry>
<entry key="security.http.maxRequests.window">60</entry>

也可以通过 Nginx 层做更精细的限流:

1
2
3
4
5
6
7
# Nginx 限流配置
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

location /ords/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:8080/ords/;
}

3.4 示例应用

3.4.1 CRUD API 完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 创建示例表
CREATE TABLE api_demo.orders (
order_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_id NUMBER NOT NULL,
order_date DATE DEFAULT SYSDATE,
total_amount NUMBER(10,2),
status VARCHAR2(20) DEFAULT 'PENDING',
created_at TIMESTAMP DEFAULT SYSTIMESTAMP,
updated_at TIMESTAMP DEFAULT SYSTIMESTAMP
);

-- 启用 AutoREST
BEGIN
ORDS.ENABLE_OBJECT(
p_enabled => TRUE,
p_schema => 'HR',
p_object => 'ORDERS',
p_object_type => 'TABLE',
p_object_alias => 'orders'
);
COMMIT;
END;
/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# CREATE - 创建订单
curl -X POST "https://api.example.com/ords/hr/orders/" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"customer_id": 1001,
"total_amount": 299.99,
"status": "PENDING"
}'

# READ - 查询订单列表(分页)
curl -X GET "https://api.example.com/ords/hr/orders/?limit=10&offset=0" \
-H "Authorization: Bearer <token>"

# READ - 查询单个订单
curl -X GET "https://api.example.com/ords/hr/orders/1" \
-H "Authorization: Bearer <token>"

# UPDATE - 更新订单状态
curl -X PUT "https://api.example.com/ords/hr/orders/1" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"status": "COMPLETED",
"total_amount": 350.00
}'

# DELETE - 删除订单
curl -X DELETE "https://api.example.com/ords/hr/orders/1" \
-H "Authorization: Bearer <token>"

3.4.2 PL/SQL API 示例

将存储过程暴露为 REST API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
-- 创建存储过程:计算部门薪资统计
CREATE OR REPLACE PROCEDURE hr.get_dept_salary_stats(
p_dept_id IN NUMBER,
p_avg_salary OUT NUMBER,
p_max_salary OUT NUMBER,
p_min_salary OUT NUMBER,
p_emp_count OUT NUMBER
) AS
BEGIN
SELECT AVG(salary), MAX(salary), MIN(salary), COUNT(*)
INTO p_avg_salary, p_max_salary, p_min_salary, p_emp_count
FROM employees
WHERE department_id = p_dept_id;
END;
/

-- 暴露为 REST API
BEGIN
ORDS.DEFINE_MODULE(
p_module_name => 'salary.api',
p_base_path => '/salary/'
);
COMMIT;
END;
/

BEGIN
ORDS.DEFINE_TEMPLATE(
p_module_name => 'salary.api',
p_pattern => 'dept-stats'
);
COMMIT;
END;
/

BEGIN
ORDS.DEFINE_HANDLER(
p_module_name => 'salary.api',
p_pattern => 'dept-stats',
p_method => 'POST',
p_source_type => 'plsql/block',
p_source => 'BEGIN
hr.get_dept_salary_stats(
p_dept_id => :dept_id,
p_avg_salary => :avg_salary,
p_max_salary => :max_salary,
p_min_salary => :min_salary,
p_emp_count => :emp_count
);
END;'
);
COMMIT;
END;
/
1
2
3
4
5
# 调用 PL/SQL API
curl -X POST "https://api.example.com/ords/hr/salary/dept-stats" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"dept_id": 50}'

3.4.3 批量操作 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-- 创建批量插入的 PL/SQL 过程
CREATE OR REPLACE PROCEDURE hr.bulk_insert_orders(
p_orders IN CLOB
) AS
l_count NUMBER;
BEGIN
INSERT INTO api_demo.orders (customer_id, total_amount, status)
SELECT jt.customer_id, jt.total_amount, jt.status
FROM JSON_TABLE(p_orders, '$[*]'
COLUMNS (
customer_id NUMBER PATH '$.customer_id',
total_amount NUMBER PATH '$.total_amount',
status VARCHAR2(20) PATH '$.status'
)
) jt;

l_count := SQL%ROWCOUNT;

-- 通过 ORDS 响应返回插入数量
OWA_UTIL.STATUS_LINE(201, 'Created');
HTP.PRINT('{"inserted": ' || l_count || '}');
END;
/

-- 暴露为 API
BEGIN
ORDS.DEFINE_HANDLER(
p_module_name => 'orders.api',
p_pattern => 'bulk',
p_method => 'POST',
p_source_type => 'plsql/block',
p_source => 'BEGIN hr.bulk_insert_orders(:body_text); END;'
);
COMMIT;
END;
/
1
2
3
4
5
6
7
8
9
# 批量插入
curl -X POST "https://api.example.com/ords/hr/orders/bulk" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '[
{"customer_id": 1001, "total_amount": 199.99, "status": "PENDING"},
{"customer_id": 1002, "total_amount": 499.99, "status": "PENDING"},
{"customer_id": 1003, "total_amount": 899.99, "status": "CONFIRMED"}
]'

四、结果验证

4.1 API 调用测试

验证 AutoREST 端点是否正常工作:

1
2
3
4
5
6
7
8
9
10
11
# 健康检查
curl -s -o /dev/null -w "%{http_code}" "https://api.example.com/ords/hr/employees/"
# 期望输出: 200

# 验证 JSON 响应格式
curl -s "https://api.example.com/ords/hr/employees/" | python3 -m json.tool
# 期望输出: 包含 items, hasMore, limit, offset, count, links 等字段

# 验证分页
curl -s "https://api.example.com/ords/hr/employees/?limit=5&offset=5" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Count: {d[\"count\"]}, HasMore: {d[\"hasMore\"]}')"

4.2 性能测试

使用 Apache Bench 或 wrk 进行简单性能测试:

1
2
3
4
5
6
7
8
9
10
11
12
# 安装 ab
sudo yum install -y httpd-tools

# 基准测试:100 并发,共 10000 请求
ab -n 10000 -c 100 \
-H "Authorization: Bearer <token>" \
"https://api.example.com/ords/hr/employees/"

# 期望结果(参考值):
# Requests per second: 2000-5000 (取决于查询复杂度和数据库性能)
# Time per request: 20-50ms (mean)
# Failed requests: 0

4.3 安全测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 测试未认证访问(应返回 401)
curl -s -o /dev/null -w "%{http_code}" "https://api.example.com/ords/hr/employees/"
# 期望: 401

# 测试 SQL 注入防护
curl -s "https://api.example.com/ords/hr/employees/1%20OR%201=1"
# 期望: 400 Bad Request 或空结果(ORDS 自动参数化,防止注入)

# 测试无效 Token
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer invalid_token" \
"https://api.example.com/ords/hr/employees/"
# 期望: 401

# 测试跨域请求
curl -s -D- -o /dev/null \
-H "Origin: https://evil.example.com" \
"https://api.example.com/ords/hr/employees/"
# 检查 CORS 头是否符合预期配置

五、经验总结

5.1 ORDS 部署最佳实践

  1. 生产环境使用 Tomcat 或 WebLogic:独立模式(Standalone)适合开发和测试,生产环境建议部署在 Tomcat/WLS 上,配合 Nginx 反向代理和负载均衡。这样可以获得更好的稳定性、监控能力和故障恢复能力。Tomcat 的线程池管理、连接超时配置等特性,能够有效防止资源耗尽的情况发生。

  2. 连接池参数调优:连接池是 ORDS 性能的核心,需要根据实际的并发量和查询响应时间来精心调整。初始连接数不宜过大,避免数据库启动时的资源压力;最大连接数需要考虑数据库的最大会话数限制,一般建议设置为预期并发数的 1.5 倍。

1
2
3
4
5
6
7
# connection pool 配置
jdbc.InitialLimit=5
jdbc.MinLimit=5
jdbc.MaxLimit=50
jdbc.MaxStatementsLimit=10
jdbc.InactivityTimeout=300
jdbc.MaxConnectionReuseCount=10000
  1. 启用 AWR/ASH 监控:ORDS 执行的 SQL 会出现在数据库 AWR 报告中,便于性能分析和调优。

  2. 日志配置

1
2
3
4
# 开启调试日志(排查问题时)
log.level=INFO
# 生产环境建议
log.level=WARN

5.2 API 设计规范

  1. URL 命名:使用小写、复数名词(/employees/ 而非 /Employee/),保持命名的一致性。URL 层级不宜过深,一般不超过三层。资源名称应使用名词而非动词,HTTP 方法本身就表达了操作意图
  2. 版本控制:通过 Module 的 base_path 实现(/v1/emp//v2/emp/),当 API 发生不兼容变更时创建新版本。旧版本应保持一段时间的向后兼容,给调用方足够的迁移时间
  3. 分页:统一使用 limit + offset 参数,响应中包含 hasMore 字段和总数信息。对于大数据量的接口,建议设置合理的默认分页大小和最大分页大小限制
  4. 错误处理:在 PL/SQL Handler 中使用 OWA_UTIL.STATUS_LINE 设置正确的 HTTP 状态码。业务异常返回 4xx 系列,系统异常返回 5xx 系列。响应体中应包含清晰的错误码和错误描述,便于调用方定位问题
  5. 响应格式:始终返回 JSON,复杂查询考虑返回自定义 JSON 结构而非默认格式。对于需要返回大量数据的场景,可以考虑使用流式响应或分批返回

5.3 性能优化建议

  1. 减少 N+1 查询:使用 JOIN 查询而非逐条查询,Handler 中的 SQL 就是你的 DAO 层。尽量在一个 Handler 中完成所有数据的获取,避免客户端多次调用 API 来组装数据
  2. 合理设置分页items_per_page 不宜过大,默认 25 较合理,最大不超过 500。对于大数据量的导出场景,建议使用游标或流式处理,而非一次性加载全部数据
  3. 使用绑定变量:ORDS 自动处理参数绑定,无需担心 SQL 注入问题,同时也能享受绑定变量带来的游标共享优势,减少数据库的硬解析开销
  4. 索引优化:确保 REST API 查询涉及的列有合适的索引。对于频繁过滤的列、排序列和关联列,都应该建立相应的索引。使用 AWR 报告分析 ORDS 生成的 SQL 执行计划,找出性能瓶颈
  5. 结果缓存:对于变化不频繁的数据,通过 HTTP Cache-Control 头实现客户端缓存,减少不必要的重复请求。同时可以在 Nginx 层实现响应缓存,进一步降低数据库压力

5.4 与 API Gateway 集成

在企业级部署中,建议将 ORDS 置于 API Gateway 之后。这种分层架构能够充分发挥各组件的优势,实现关注点分离:

1
客户端 → API Gateway (Kong/Apigee/AWS API GW) → Nginx → ORDS → Oracle DB

API Gateway 提供的关键能力:

  • 统一认证:JWT 验证、OAuth2 Token 代理、API Key 管理等。将认证逻辑从 ORDS 中剥离出来,由 Gateway 统一处理,降低了 ORDS 的复杂度
  • 限流熔断:比 ORDS 自身的限流更强大,支持基于客户端、API 路径、时间窗口等多维度的限流策略。熔断机制可以在数据库异常时自动降级,保护后端系统
  • 请求转换:在 Gateway 层做请求/响应格式转换,使得同一个 API 可以适配不同客户端的数据格式需求
  • 监控分析:API 调用量、延迟分布、错误率、吞吐量等关键指标的实时监控和告警,为容量规划和性能调优提供数据支撑
  • 灰度发布:通过路由规则实现 API 版本灰度发布,新版本 API 可以先对少量流量开放,验证无误后再全量切换

5.5 注意事项

  1. 不要暴露敏感列:AutoREST 会暴露表的所有列,包括可能包含敏感信息的字段(如密码哈希、身份证号等)。生产环境务必使用手动 Handler 精确控制返回字段,或者通过视图来过滤敏感列
  2. 事务控制:AutoREST 的 POST/PUT/DELETE 操作会自动提交事务,对于需要跨表操作或复杂业务逻辑的场景,建议使用 PL/SQL Handler 显式控制事务边界,确保数据一致性
  3. 并发安全:高并发场景下需要注意数据库锁和连接池耗尽问题。建议在 Handler 中使用 FOR UPDATE SKIP LOCKED 等机制避免锁等待,同时合理配置连接池的最大连接数
  4. 日志审计:启用 ORDS 的访问日志和数据库审计功能,记录所有 API 的调用时间、调用者、请求参数和响应状态。这对于安全审计、问题排查和合规要求都至关重要
  5. 监控告警:对 ORDS 的关键指标进行监控,包括连接池使用率、请求响应时间、错误率等。设置合理的告警阈值,在问题恶化之前及时发现和处理

总结:ORDS 是 Oracle 生态中被严重低估的利器。对于以 Oracle 数据库为核心的系统,它能够以极低的开发成本将数据库能力暴露为标准化的 RESTful API,特别适合 DBA 团队主导的数据服务化项目。通过本文的实战讲解,我们从架构原理到安装配置,从 API 创建到安全加固,从性能优化到生产部署,完整覆盖了 ORDS 的核心使用场景。配合合理的安全配置和 API Gateway,ORDS 完全有能力支撑生产级别的 API 服务。如果你的团队正在为数据库服务化而苦恼,不妨试试 ORDS——它可能会给你带来意想不到的惊喜。

一、问题背景

在企业级 Oracle 数据库运维中,监控是保障业务连续性的第一道防线。然而,大多数 DBA 在实际工作中都会遇到这样的困境:

默认监控模板的局限性:无论是 Zabbix 社区提供的 Oracle Template,还是 Prometheus 官方 Exporter 的默认采集项,都无法覆盖生产环境中的全部监控需求。例如,默认模板通常只关注基础的表空间使用率、连接数等通用指标,而对于 DG 同步延迟、RAC 节点间心跳、特定业务 SQL 的执行效率等关键指标,往往需要 DBA 自行定制。

业务定制化的监控需求:不同的业务场景对监控的粒度和维度要求各不相同。电商系统关注并发会话与锁等待,金融系统关注归档日志生成速率与 Data Guard 延迟,而批处理系统则更关注 TOP SQL 的执行计划变化。一刀切的默认模板无法满足这些差异化需求。

可视化对运维决策的价值:监控不仅仅是”出问题时告警”,更重要的是通过历史趋势分析支撑容量规划、性能优化等主动运维决策。一个设计良好的 Grafana Dashboard 可以让 DBA 在数秒内掌握整个数据库集群的健康状态。

本文将从 Zabbix 和 Prometheus 两大主流监控平台出发,详细介绍 Oracle 监控的定制化方法,包括自定义监控项、自动发现规则、告警触发器以及 Grafana 可视化 Dashboard 的搭建。

二、理论分析

2.1 Zabbix 监控架构

Zabbix 采用经典的三层架构:Agent → Server → Web

  • Agent:部署在被监控主机上,负责采集本地数据。对于 Oracle 监控,Agent 通过 UserParameter 调用自定义脚本连接数据库采集指标。
  • Server:接收 Agent 上报的数据,执行触发器评估、告警通知等逻辑。
  • Web:提供可视化界面,支持 Dashboard 展示、配置管理等功能。

Zabbix 的强大之处在于 UserParameter 机制。通过 UserParameter,DBA 可以将任意 Shell/Python 脚本注册为监控项,由 Agent 定时调用并上报结果。这为 Oracle 自定义监控提供了极大的灵活性。

此外,Zabbix 的 Low-Level Discovery(LLD) 功能可以自动发现数据库中的表空间、PDB 等动态对象,避免手动逐一配置监控项。

2.2 Prometheus + Grafana

Prometheus 采用 Pull 模型,通过 Exporter 暴露 HTTP 端点供 Prometheus Server 拉取指标数据。对于 Oracle 监控,最常用的 Exporter 是 oracledb_exporter(由 iamseth/oracledb_exporter 项目维护)。

Prometheus 的查询语言 PromQL 非常强大,支持丰富的聚合、计算和时间序列操作。例如,可以用一条 PromQL 计算过去 1 小时内归档日志的平均生成速率:

1
rate(oracle_archive_log_generated_bytes_total[1h])

Grafana 作为 Prometheus 的可视化前端,提供了丰富的图表类型和模板变量支持。社区已有大量成熟的 Oracle Dashboard 模板(如 ID 为 3333 的 Dashboard),可以快速导入并根据实际需求定制。

2.3 Oracle 监控指标分类

生产环境中,Oracle 监控指标可以按层级分为以下几类:

指标类别 典型指标 采集频率建议
实例级 SGA/PGA 使用率、DB Time、Logical/Physical Reads 30s ~ 1min
表空间级 使用率、增长速率、剩余空间 5min
SQL 级 TOP SQL Elapsed Time、Buffer Gets、Executions 5min
DG 级 Apply Lag、Transport Lag、Gap Status 1min
RAC 级 Interconnect Traffic、GC Wait、Instance Status 1min

合理分类指标有助于设置差异化的采集频率和告警阈值,避免对数据库造成不必要的性能开销。

三、实战操作

3.1 Zabbix 自定义监控项

3.1.1 UserParameter 配置

在 Zabbix Agent 配置文件中定义 UserParameter。以表空间使用率为例:

1
2
3
4
5
# /etc/zabbix/zabbix_agentd.d/oracle_tablespace.conf
UserParameter=oracle.tablespace.usage[*],/etc/zabbix/scripts/oracle_tablespace.sh $1 $2

# 通用 Oracle 指标采集入口
UserParameter=oracle.custom.query[*],/etc/zabbix/scripts/oracle_monitor.sh $1 $2

3.1.2 Shell 脚本实现

以下是表空间使用率采集脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
# /etc/zabbix/scripts/oracle_tablespace.sh
# 用法: oracle_tablespace.sh <ORACLE_SID> <TABLESPACE_NAME>

export ORACLE_HOME=/u01/app/oracle/product/19c/dbhome_1
export ORACLE_SID=$1
export PATH=$ORACLE_HOME/bin:$PATH

TABLESPACE_NAME=$2

sqlplus -S / as sysdba <<EOF | tail -1
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF
SELECT ROUND((used.bytes / total.bytes) * 100, 2)
FROM
(SELECT tablespace_name, SUM(bytes) bytes
FROM dba_data_files
WHERE tablespace_name = UPPER('${TABLESPACE_NAME}')
GROUP BY tablespace_name) total,
(SELECT tablespace_name, SUM(bytes) bytes
FROM dba_segments
WHERE tablespace_name = UPPER('${TABLESPACE_NAME}')
GROUP BY tablespace_name) used
WHERE total.tablespace_name = used.tablespace_name(+);
EOF

通用监控脚本,支持多种指标查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash
# /etc/zabbix/scripts/oracle_monitor.sh
export ORACLE_HOME=/u01/app/oracle/product/19c/dbhome_1
export ORACLE_SID=$1
export PATH=$ORACLE_HOME/bin:$PATH

METRIC=$2

case "$METRIC" in
"sessions")
sqlplus -S / as sysdba <<'EOF' | tail -1
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF
SELECT COUNT(*) FROM v\$session WHERE status = 'ACTIVE';
EOF
;;
"archive_rate")
sqlplus -S / as sysdba <<'EOF' | tail -1
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF
SELECT ROUND(SUM(blocks * block_size) / 1024 / 1024, 2)
FROM v\$archived_log
WHERE first_time > SYSDATE - 1/24
AND dest_id = 1;
EOF
;;
"dg_lag")
sqlplus -S / as sysdba <<'EOF' | tail -1
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF
SELECT VALUE FROM v\$dataguard_stats WHERE NAME = 'apply lag';
EOF
;;
esac

3.1.3 自动发现规则(LLD)

为自动发现所有表空间并创建监控项,需要定义发现规则:

1
2
3
4
5
6
7
8
// discovery_rule 返回的 JSON 格式
{
"data": [
"{#TABLESPACE_NAME}": "USERS"},
"{#TABLESPACE_NAME}": "SYSTEM"},
"{#TABLESPACE_NAME}": "SYSAUX"}
]
}

发现规则的 UserParameter 配置:

1
2
3
# zabbix_agentd.d/oracle_discovery.conf
UserParameter=oracle.discovery.tablespace[*],/etc/zabbix/scripts/oracle_discovery.sh $1 tablespace
UserParameter=oracle.discovery.pdb[*],/etc/zabbix/scripts/oracle_discovery.sh $1 pdb

发现脚本示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
# /etc/zabbix/scripts/oracle_discovery.sh
export ORACLE_HOME=/u01/app/oracle/product/19c/dbhome_1
export ORACLE_SID=$1
export PATH=$ORACLE_HOME/bin:$PATH

OBJECT_TYPE=$2

case "$OBJECT_TYPE" in
"tablespace")
sqlplus -S / as sysdba <<'EOF'
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF
SELECT '{"data":[' FROM dual;
SELECT LISTAGG('{"{#TABLESPACE_NAME}":"' || tablespace_name || '"}', ',')
WITHIN GROUP (ORDER BY tablespace_name)
FROM dba_tablespaces WHERE contents = 'PERMANENT';
SELECT ']}' FROM dual;
EOF
;;
esac

3.1.4 告警触发器

在 Zabbix Web 界面中配置触发器:

1
2
3
4
5
6
7
8
9
10
11
12
# 表空间使用率超过 85% 为 Warning
{Template Oracle:oracle.tablespace.usage[{#TABLESPACE_NAME}].last()}>85

# 表空间使用率超过 95% 为 Disaster
{Template Oracle:oracle.tablespace.usage[{#TABLESPACE_NAME}].last()}>95

# DG Apply Lag 超过 300 秒告警
{Template Oracle:oracle.custom.query[ORCL,dg_lag].last()}>300

# Active Session 突增告警(超过基线 2 倍)
{Template Oracle:oracle.custom.query[ORCL,sessions].avg(10m)} >
{Template Oracle:oracle.custom.query[ORCL,sessions].avg(1h)} * 2

3.2 Prometheus 配置

3.2.1 oracledb_exporter 部署

1
2
3
4
5
6
7
8
9
10
11
# 下载 exporter
wget https://github.com/iamseth/oracledb_exporter/releases/download/0.6.0/oracledb_exporter-0.6.0.linux-amd64.tar.gz
tar xzf oracledb_exporter-0.6.0.linux-amd64.tar.gz
mv oracledb_exporter /usr/local/bin/

# 配置环境变量
cat > /etc/oracledb_exporter.env <<'EOF'
DATA_SOURCE_NAME=oracle://exporter:password@localhost:1521/ORCL
LD_LIBRARY_PATH=/u01/app/oracle/instantclient_19_21
CUSTOM_METRICS=/etc/oracledb_exporter/custom-metrics.toml
EOF

自定义指标配置(custom-metrics.toml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# /etc/oracledb_exporter/custom-metrics.toml

# 表空间使用率
[[metric]]
context = "oracle_tablespace"
metricsdesc = { pct_used = "Tablespace percent used", free_mb = "Free space in MB", total_mb = "Total space in MB" }
request = """
SELECT
t.tablespace_name,
ROUND((1 - (f.free_bytes / t.total_bytes)) * 100, 2) as pct_used,
ROUND(f.free_bytes / 1024 / 1024, 2) as free_mb,
ROUND(t.total_bytes / 1024 / 1024, 2) as total_mb
FROM
(SELECT tablespace_name, SUM(bytes) total_bytes
FROM dba_data_files GROUP BY tablespace_name) t,
(SELECT tablespace_name, SUM(bytes) free_bytes
FROM dba_free_space GROUP BY tablespace_name) f
WHERE t.tablespace_name = f.tablespace_name(+)
"""

# Active Session Count
[[metric]]
context = "oracle_session"
metricsdesc = { active_count = "Number of active sessions", total_count = "Total sessions" }
request = """
SELECT
SUM(CASE WHEN status='ACTIVE' THEN 1 ELSE 0 END) as active_count,
COUNT(*) as total_count
FROM v$session WHERE type = 'USER'
"""

# DG Stats
[[metric]]
context = "oracle_dg"
metricsdesc = { apply_lag_seconds = "Data Guard apply lag in seconds", transport_lag_seconds = "Transport lag in seconds" }
request = """
SELECT
EXTRACT(DAY FROM TO_DSINTERVAL(VALUE)) * 86400 +
EXTRACT(HOUR FROM TO_DSINTERVAL(VALUE)) * 3600 +
EXTRACT(MINUTE FROM TO_DSINTERVAL(VALUE)) * 60 +
EXTRACT(SECOND FROM TO_DSINTERVAL(VALUE)) as apply_lag_seconds
FROM v$dataguard_stats WHERE name = 'apply lag'
"""

# Top SQL by Elapsed Time
[[metric]]
context = "oracle_top_sql"
metricsdesc = { elapsed_seconds = "SQL elapsed time", executions = "SQL executions", buffer_gets = "Buffer gets" }
request = """
SELECT
ROWNUM as sql_rank,
ROUND(elapsed_time / 1000000, 2) as elapsed_seconds,
executions,
buffer_gets
FROM (
SELECT elapsed_time, executions, buffer_gets
FROM v$sql ORDER BY elapsed_time DESC
) WHERE ROWNUM <= 10
"""

配置 systemd 服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# /etc/systemd/system/oracledb_exporter.service
[Unit]
Description=Oracle DB Exporter
After=network.target

[Service]
EnvironmentFile=/etc/oracledb_exporter.env
ExecStart=/usr/local/bin/oracledb_exporter \
--web.listen-address=:9161 \
--log.level=info
Restart=always
User=oracle

[Install]
WantedBy=multi-user.target
1
2
systemctl daemon-reload
systemctl enable --now oracledb_exporter

3.2.2 Prometheus 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# prometheus.yml
scrape_configs:
- job_name: 'oracle'
scrape_interval: 30s
scrape_timeout: 10s
static_configs:
- targets:
- 'db-host-1:9161'
- 'db-host-2:9161'
labels:
env: 'production'
metric_relabel_configs:
- source_labels: [__name__]
regex: 'oracle_tablespace_(.+)'
target_label: 'tablespace'

3.2.3 Grafana Dashboard 配置

在 Grafana 中添加 Prometheus 数据源后,可以使用社区 Dashboard 模板(ID: 3333)或自建 Dashboard。常用 PromQL 查询:

1
2
3
4
5
6
7
8
9
10
11
# 表空间使用率 Top 10
topk(10, oracle_tablespace_pct_used)

# 1 小时内归档日志生成速率 (MB/h)
rate(oracle_redo_size_bytes_total[1h]) / 1024 / 1024 * 3600

# Session 利用率
oracle_session_active_count / oracle_parameter_processes * 100

# DG Apply Lag 趋势
oracle_dg_apply_lag_seconds

Dashboard 中添加模板变量以支持多实例切换:

1
2
3
# 变量名: instance
# 类型: Query
# 查询: label_values(up{job="oracle"}, instance)

3.3 常用监控脚本汇总

以下汇总生产环境中最常用的监控采集 SQL,可在 Zabbix UserParameter 或 Prometheus custom-metrics.toml 中直接使用:

表空间使用率

1
2
3
4
5
6
7
8
SELECT tablespace_name,
ROUND((1 - NVL(f.free, 0) / t.total) * 100, 2) pct_used,
ROUND(t.total / 1024 / 1024, 2) total_mb
FROM (SELECT tablespace_name, SUM(bytes) total
FROM dba_data_files GROUP BY tablespace_name) t,
(SELECT tablespace_name, SUM(bytes) free
FROM dba_free_space GROUP BY tablespace_name) f
WHERE t.tablespace_name = f.tablespace_name(+);

归档日志生成速率(过去 1 小时,MB/h):

1
2
3
SELECT ROUND(SUM(blocks * block_size) / 1024 / 1024, 2) archive_mb
FROM v$archived_log
WHERE first_time > SYSDATE - 1/24 AND dest_id = 1;

DG 延迟

1
2
SELECT name, value FROM v$dataguard_stats
WHERE name IN ('apply lag', 'transport lag');

TOP SQL(按 Elapsed Time 排序):

1
2
3
SELECT sql_id, sql_text, elapsed_time, executions,
ROUND(elapsed_time / NULLIF(executions, 0) / 1000000, 2) avg_sec
FROM v$sql ORDER BY elapsed_time DESC FETCH FIRST 10 ROWS ONLY;

四、结果验证

4.1 监控数据采集验证

部署完成后,需要验证数据采集的正确性:

1
2
3
4
5
6
7
# Zabbix Agent 测试
zabbix_agentd -t oracle.tablespace.usage[ORCL,USERS]
# 预期输出: [t|85.23]

# Prometheus Exporter 测试
curl -s http://localhost:9161/metrics | grep oracle_tablespace
# 预期输出: oracle_tablespace_pct_used{tablespace="USERS"} 85.23

4.2 告警触发测试

模拟告警场景进行测试:

1
2
3
# 临时调低阈值验证告警触发
# 在 Zabbix 中将表空间告警阈值临时设为 10%,观察是否触发
# 在 Prometheus Alertmanager 中配置 test alert rule

验证告警通知是否正常发送到指定渠道(邮件、企业微信、钉钉等)。

4.3 Dashboard 可视化验证

检查 Grafana Dashboard 各面板数据是否正常展示:

  1. 打开 Dashboard,确认所有面板有数据渲染
  2. 切换模板变量(实例、表空间),确认数据联动正确
  3. 检查时间范围选择是否影响图表展示
  4. 验证告警阈值标注线是否正确显示

五、经验总结

5.1 监控指标选择建议

  • 必选指标:表空间使用率、连接数、DG 延迟、归档日志生成速率
  • 推荐指标:SGA/PGA 命中率、TOP SQL、锁等待、RAC 间通信
  • 可选指标:索引使用率、审计日志量、备份状态

指标并非越多越好,过多的采集项会增加数据库负担。建议根据业务实际需求做减法。

5.2 告警阈值设置

指标 Warning 阈值 Disaster 阈值 说明
表空间使用率 85% 95% 需关注增长趋势
DG Apply Lag 5 min 30 min 金融场景可适当收紧
归档生成速率 5 GB/h 20 GB/h 需结合历史基线
Active Sessions 基线×2 基线×5 基线需运行 2 周以上

建议采用动态阈值策略:以过去 2~4 周的历史数据为基线,告警阈值设置为基线的 N 倍,而非固定绝对值。

5.3 Dashboard 设计原则

  1. 分层展示:总览 → 集群级 → 实例级 → SQL 级,支持逐层下钻
  2. 颜色编码:绿色(正常)→ 黄色(Warning)→ 红色(Disaster),一目了然
  3. 时间对比:支持同比/环比对比,便于发现异常趋势
  4. 变量联动:利用 Grafana Template Variables 实现多实例、多维度切换
  5. 移动适配:考虑在手机端查看的场景,关键面板放在首屏

好的监控体系不是一蹴而就的,需要在生产实践中持续迭代优化。建议每季度回顾一次监控策略,结合业务变化和历史告警记录,不断调整采集指标和告警阈值,让监控真正成为数据库稳定运行的坚实保障。

一、问题背景

作为一名 Oracle DBA,日常巡检是最基础也是最重要的工作职责之一。无论是生产环境还是测试环境,每天登录数据库检查实例状态、表空间使用率、备份完成情况、告警日志中的错误信息,这些都是确保数据库稳定运行的基本保障。

然而,手工巡检存在几个明显的痛点:

  1. 效率低下:管理的数据库实例越多,每天花费在巡检上的时间就越长。假设管理 20 个实例,每个实例巡检 10 分钟,一天仅巡检就要 3 个多小时。
  2. 容易遗漏:人工操作难免疲劳,尤其在节假日值班时,某些检查项可能被跳过。
  3. 缺乏历史记录:手工巡检的结果难以标准化存储,无法进行趋势分析。
  4. 响应滞后:问题往往在巡检时才发现,无法做到实时或准实时告警。

自动巡检的价值在于:

  • 标准化:所有检查项固定,不会遗漏
  • 可追溯:巡检结果自动存储,支持历史对比
  • 告警前置:发现异常立即发送邮件/短信通知
  • 释放人力:DBA 可以将精力集中在更有价值的工作上

本文将介绍一套完整的 Oracle 自动巡检方案,涵盖 Shell 脚本和 Python 脚本两种实现,覆盖实例状态、表空间、ASM 磁盘组、告警日志、备份状态、归档日志等核心检查项。

阅读全文 »

一、问题背景

补丁管理是 Oracle DBA 日常运维中最核心、最高频的工作之一。一个成熟的 DBA,不仅要能熟练安装补丁,更要掌握补丁从下载、分析、冲突检测、应用到回滚的完整生命周期管理。在生产环境中,任何一次补丁操作的失误都可能导致数据库无法启动、业务中断,甚至数据丢失。

Oracle 的补丁体系经历了一次重要的演变:从 12c 之前的 PSU (Patch Set Update) 体系,转变为 12c 及之后的 RU (Release Update) 体系。理解这一演变过程,对于正确选择和应用补丁至关重要。

补丁应用失败的生产风险不容小觑。常见问题包括:补丁冲突导致安装失败、patch 后 datapatch 未执行导致数据字典不一致、RAC 环境节点间版本不一致等。因此,建立标准化的补丁管理流程是每个 DBA 团队的必修课。


二、理论分析

2.1 Oracle 补丁体系

Oracle 提供了多种类型的补丁,理解它们的定位和适用场景是补丁管理的基础:

补丁类型 说明 适用场景
CPU (Critical Patch Update) 关键安全补丁,季度发布 修复安全漏洞
PSU (Patch Set Update) 包含 CPU + 重要 Bug 修复(11g 及之前) 11g 及更早版本的定期维护
RU (Release Update) 替代 PSU 的季度累积补丁(12.2+) 12c R2 及之后版本的定期维护
RUR (Release Update Revision) RU 的修订版,仅含安全修复和回退修复 不想升级 RU 但仍需安全修复
One-off Patch (Interim Patch) 针对特定 Bug 的单点修复 解决特定问题,不包含在 RU 中

从 12.2 版本开始,Oracle 用 RU 替代了 PSU。RU 每季度发布,包含安全修复和经过充分测试的 Bug 修复。RUR 则是 RU 的修订版,适用于希望保持当前 RU 版本但仍需接收安全修复的场景。

补丁编号的演进规律:

  • 11g: PSU 11.2.0.4.x(如 11.2.0.4.190115)
  • 12.1: PSU 12.1.0.2.x
  • 12.2+: RU 12.2.0.1.x、19.x.x(如 19.23.0.0.0)

2.2 OPatch 工具

OPatch 是 Oracle 提供的补丁管理命令行工具,位于 $ORACLE_HOME/OPatch/ 目录下。它的主要功能包括:

OPatch 版本管理

OPatch 自身也需要定期更新。在应用任何补丁之前,首先需要确认 OPatch 版本满足要求:

1
2
3
# 查看当前 OPatch 版本
$ORACLE_HOME/OPatch/opatch version
$ORACLE_HOME/OPatch/opatch lsinventory

MOS 文档通常会指明所需的最低 OPatch 版本。版本不满足时,需要先升级 OPatch:

1
2
3
4
5
# 备份旧版 OPatch
mv $ORACLE_HOME/OPatch $ORACLE_HOME/OPatch.bak

# 解压新版 OPatch
unzip p6880880_<platform>.zip -d $ORACLE_HOME/

补丁冲突检测

OPatch 在应用补丁前会自动进行冲突检测,检查新补丁是否与已安装的补丁存在冲突:

1
2
3
# 预检查(dry-run),不实际应用补丁
cd <patch_number>
$ORACLE_HOME/OPatch/opatch prereq CheckConflictAgainstOHWithDetail -ph ./

冲突分为两类:

  • Duplicated patch conflict: 新补丁是已安装补丁的子集,已安装的补丁版本更高
  • Subset patch conflict: 已安装的补丁是新补丁的子集,需要用新补丁替换

补丁回滚机制

OPatch 支持通过补丁编号回滚已安装的补丁:

1
2
# 回滚指定补丁
$ORACLE_HOME/OPatch/opatch rollback -id <patch_number>

回滚时 OPatch 会使用 $ORACLE_HOME/.patch_storage/<patch_number>/ 下保存的原始文件进行恢复。

2.3 OPlan

OPlan (Oracle Patch Planning Tool) 是 Oracle 提供的补丁规划和冲突预检测工具,用于在正式应用补丁之前进行全面分析,生成详细的分析报告。

OPlan 的作用

  • 在补丁下载前分析目标环境与补丁的兼容性
  • 检测补丁与已安装补丁的冲突
  • 生成应用补丁所需的操作步骤
  • 评估补丁对系统的影响

OPlan 使用流程

1
2
3
4
5
6
7
8
9
10
11
# 1. 下载 OPlan 工具(从 MOS 下载 oplan 包)
# 解压到指定目录
unzip oplan_<version>.zip -d /opt/oracle/oplan/

# 2. 执行分析
# opcheck 模式:分析补丁与当前环境的兼容性
$ORACLE_HOME/OPatch/oplan/oplan/opcheck <patch_location> <target_version>

# 3. 生成报告
# report 模式:生成详细的补丁应用步骤
$ORACLE_HOME/OPatch/oplan/oplan/report <patch_location> <target_version>

生成的报告位于 <patch_location>/opcheck_report.htmlreport.html,包含以下关键信息:

  • 环境信息(Oracle Home、数据库版本、已安装补丁列表)
  • 冲突检测结果
  • 推荐的操作步骤
  • 预计停机时间

2.4 RAC 滚动补丁

Rolling Patch(滚动补丁) 是 RAC 环境中实现零停机补丁应用的关键技术。其原理是逐节点应用补丁,每次只停止一个节点进行补丁操作,其余节点继续提供服务。

Rolling Patch 的前提条件

  • 补丁必须标记为 rolling capable
  • 使用 opatch auto 或手动逐节点操作
  • 确保集群在任一节点停止时仍能满足业务负载

OPatch auto (RAC)

Oracle 提供了 opatch auto 命令来自动化 RAC 环境的滚动补丁过程:

1
2
3
4
5
6
# opatch auto 自动处理以下步骤:
# 1. 停止当前节点的数据库实例
# 2. 应用 GI 和 DB Home 补丁
# 3. 执行 datapatch
# 4. 重启当前节点实例
# 5. 移动到下一个节点重复以上步骤

零停机补丁策略

真正的零停机需要配合应用层的连接管理。典型策略包括:

  1. Service-based failover:通过 Oracle Service 实现连接漂移
  2. 节点逐一下线:每次从集群中移除一个节点进行补丁
  3. 滚动重启:补丁完成后节点重新加入集群
  4. 数据字典更新:通过 datapatch 在所有节点完成后统一执行

三、实战操作

3.1 补丁下载与分析

步骤一:MOS 补丁搜索

登录 My Oracle Support,通过以下方式搜索补丁:

  1. 按补丁编号搜索:已知具体 Patch Number 时直接搜索
  2. 按产品/版本搜索:选择 Product = “Oracle Database”,Release = “19.x”
  3. 推荐补丁:查看 MOS 文档 Doc ID 555.1 获取最新推荐补丁

下载补丁后上传到服务器并解压:

1
2
3
4
5
6
# 上传补丁文件
scp p36233123_190000_Linux-x86-64.zip oracle@dbserver:/u01/patches/

# 解压补丁
cd /u01/patches/
unzip p36233123_190000_Linux-x86-64.zip

步骤二:OPlan 分析报告

在正式应用前,使用 OPlan 生成分析报告:

1
2
3
4
5
6
7
8
9
10
11
# 执行 opcheck 分析
cd /opt/oracle/oplan/
export ORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1
$ORACLE_HOME/OPatch/opatch prereq CheckConflictAgainstOHWithDetail \
-phBaseDir /u01/patches/36233123

# 使用 OPlan 生成完整报告
cd /u01/patches/36233123
$ORACLE_HOME/OPatch/oplan/oplan report \
/u01/patches/36233123 \
19.0.0.0.0

步骤三:OPatch 冲突检查

1
2
3
4
5
6
7
8
9
10
11
12
13
# 确认 OPatch 版本满足要求
$ORACLE_HOME/OPatch/opatch version
# 预期输出:OPatch Version: 12.2.0.1.42

# 列出当前已安装的补丁
$ORACLE_HOME/OPatch/opatch lsinventory -detail

# 冲突检查
cd /u01/patches/36233123
$ORACLE_HOME/OPatch/opatch prereq CheckConflictAgainstOHWithDetail -ph ./

# 检查空间需求
$ORACLE_HOME/OPatch/opatch prereq CheckSystemSpace -ph ./

3.2 单机补丁应用

完整补丁应用流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# ============================================
# 1. 环境准备
# ============================================
export ORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1
export ORACLE_SID=orcl
export PATH=$ORACLE_HOME/OPatch:$PATH

# 确认数据库状态
sqlplus / as sysdba
SQL> select status from v$instance;
SQL> exit;

# 关闭数据库和监听器
srvctl stop database -d orcl
srvctl stop listener

# ============================================
# 2. 应用补丁(OPatch apply)
# ============================================
cd /u01/patches/36233123
$ORACLE_HOME/OPatch/opatch apply

# 应用过程中,OPatch 会执行以下检查:
# - 版本兼容性检查
# - 冲突检测
# - 空间检查
# - 备份原始文件
# - 应用补丁文件
# - 更新 inventory

# ============================================
# 3. 执行 datapatch(数据字典更新)
# ============================================
# 启动数据库到 OPEN 状态
sqlplus / as sysdba
SQL> startup
SQL> exit;

# 执行 datapatch 更新数据字典
cd $ORACLE_HOME/OPatch
./datapatch -verbose

# datapatch 执行内容:
# - 检查需要应用 SQL 补丁的 PDB 列表
# - 执行补丁相关的 SQL 脚本
# - 更新 DBA_REGISTRY_SQLPATCH 视图

# ============================================
# 4. 补丁后验证
# ============================================
# 确认补丁已应用
$ORACLE_HOME/OPatch/opatch lsinventory

# 确认 datapatch 执行成功
sqlplus / as sysdba
SQL> select PATCH_ID, PATCH_TYPE, ACTION, STATUS, DESCRIPTION
from DBA_REGISTRY_SQLPATCH
order by ACTION_TIME;

# 编译无效对象(可选但推荐)
sqlplus / as sysdba
SQL> @?/rdbms/admin/utlrp.sql

3.3 RAC 滚动补丁

使用 opatch auto 的完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# ============================================
# RAC 滚动补丁 - 节点1 操作
# ============================================

# 1. 确认集群状态
crsctl stat res -t

# 2. 在节点1上执行 opatch auto
# 该命令会自动完成:
# - 停止节点1上的数据库实例
# - 应用 GI Home 补丁
# - 应用 DB Home 补丁
# - 启动节点1上的数据库实例
cd /u01/patches/36233123
$ORACLE_HOME/OPatch/opatch auto /u01/patches/36233123 \
-ocmrf /u01/patches/ocm.rsp

# 3. 验证节点1补丁状态
$ORACLE_HOME/OPatch/opatch lsinventory

# ============================================
# 节点2 操作(重复以上步骤)
# ============================================
ssh oracle@racnode2
export ORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1
cd /u01/patches/36233123
$ORACLE_HOME/OPatch/opatch auto /u01/patches/36233123 \
-ocmrf /u01/patches/ocm.rsp

# ============================================
# 所有节点补丁完成后 - 执行 datapatch
# ============================================
# 仅在任意一个节点上执行一次 datapatch
# datapatch 会自动处理所有 PDB
sqlplus / as sysdba
SQL> alter pluggable database all open;
SQL> exit;

cd $ORACLE_HOME/OPatch
./datapatch -verbose

# ============================================
# RAC 零停机验证
# ============================================
# 检查所有节点补丁一致
srvctl config database -d orcl

# 确认所有实例正常
sqlplus / as sysdba
SQL> select inst_name, status from gv$instance;

# 确认数据字典更新成功
SQL> select PATCH_ID, ACTION, STATUS, DESCRIPTION
from DBA_REGISTRY_SQLPATCH
order by ACTION_TIME;

手动滚动补丁(不使用 opatch auto)

opatch auto 遇到环境特殊配置问题时,可以手动执行滚动补丁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 在节点1上操作
# 1. 停止节点1实例
srvctl stop instance -d orcl -i orcl1

# 2. 应用补丁
cd /u01/patches/36233123
$ORACLE_HOME/OPatch/opatch apply -local

# 3. 启动节点1实例
srvctl start instance -d orcl -i orcl1

# 对其他节点重复以上步骤...

# 所有节点完成后执行 datapatch
cd $ORACLE_HOME/OPatch
./datapatch -verbose

3.4 补丁回滚

当补丁应用后出现问题时,需要执行回滚操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# ============================================
# 1. 单机环境回滚
# ============================================

# 回滚 OPatch 补丁
cd /u01/patches/36233123
$ORACLE_HOME/OPatch/opatch rollback -id 36233123

# 执行 datapatch 回滚数据字典变更
sqlplus / as sysdba
SQL> startup
SQL> exit;

cd $ORACLE_HOME/OPatch
./datapatch -rollback 36233123 -verbose

# 验证回滚
$ORACLE_HOME/OPatch/opatch lsinventory
sqlplus / as sysdba
SQL> select PATCH_ID, ACTION, STATUS, DESCRIPTION
from DBA_REGISTRY_SQLPATCH
order by ACTION_TIME;

# ============================================
# 2. RAC 环境回滚
# ============================================

# 逐节点回滚
# 节点1
srvctl stop instance -d orcl -i orcl1
$ORACLE_HOME/OPatch/opatch rollback -id 36233123 -local
srvctl start instance -d orcl -i orcl1

# 节点2(重复)
# ...

# 执行 datapatch 回滚
cd $ORACLE_HOME/OPatch
./datapatch -rollback 36233123 -verbose

回滚注意事项:并非所有补丁都支持回滚。部分补丁在应用过程中修改了不可逆的数据结构,此时只能通过 RMAN 恢复或 Data Guard 切换实现回退。因此,补丁前的备份是至关重要的。


四、结果验证

补丁应用完成后,必须进行系统化的验证工作:

4.1 opatch lsinventory 验证

1
2
3
4
5
# 查看详细的补丁清单
$ORACLE_HOME/OPatch/opatch lsinventory -detail

# 查看补丁 ID 和描述
$ORACLE_HOME/OPatch/opatch lsinventory | grep -A5 "Patch"

输出应包含新应用的补丁编号、应用时间、补丁描述等信息。

4.2 DBA_REGISTRY_SQLPATCH 验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 查看所有 SQL Patch 的应用状态
SELECT patch_id,
patch_type,
action,
status,
action_time,
description
FROM dba_registry_sqlpatch
ORDER BY action_time DESC;

-- 确认无失败的补丁
SELECT COUNT(*) AS failed_count
FROM dba_registry_sqlpatch
WHERE status = 'WITH ERRORS';

4.3 补丁后功能验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 检查数据库组件状态
SELECT comp_name, version, status
FROM dba_registry
WHERE status != 'VALID';

-- 检查无效对象数量
SELECT owner, object_type, COUNT(*)
FROM dba_objects
WHERE status = 'INVALID'
GROUP BY owner, object_type;

-- 验证数据库基本功能
SELECT * FROM v$version;
SELECT instance_name, status FROM v$instance;

-- RAC 环境额外检查
SELECT inst_name, status FROM gv$instance;
SELECT name, open_mode FROM v$pdbs;

五、经验总结

1. 补丁管理流程标准化

建议制定标准化的补丁管理 SOP(Standard Operating Procedure),涵盖:

  • 补丁评估与审批流程
  • 补丁测试计划模板
  • 补丁应用检查清单(Checklist)
  • 补丁后验证脚本
  • 回滚方案与应急措施

2. 测试环境验证的重要性

任何补丁在应用到生产环境之前,必须在测试环境完成以下验证:

  • 补丁应用过程无报错
  • datapatch 执行成功
  • 核心业务功能回归测试
  • 性能基准对比测试
  • 回滚操作演练

3. 补丁回滚策略

  • OPatch rollback: 适用于大部分补丁,依赖 .patch_storage 中的备份文件
  • RMAN 恢复: 当补丁不支持回滚时的最终手段
  • Data Guard Switchover: 如果有物理备库,可通过切换实现快速回退
  • 存储快照恢复: 利用存储层快照实现秒级回退

关键提醒:确保 $ORACLE_HOME/.patch_storage/ 目录的完整性。OPatch 的回滚机制依赖此目录中保存的原始文件备份。不要手动清理此目录。

4. 补丁窗口规划

  • 季度补丁窗口:跟随 Oracle 的季度发布节奏(1月、4月、7月、10月)
  • 紧急补丁窗口:针对 Critical Patch Update 中的高危漏洞
  • 补丁时间选择:选择业务低谷期,通常为周末凌晨
  • 补丁窗口时长:预留足够的时间缓冲,建议至少 2-4 小时
  • 通知机制:提前通知相关方,确保有足够的人力支持

补丁管理是 DBA 专业能力的重要体现。通过标准化的流程、完善的测试验证和可靠的回滚策略,可以将补丁风险降到最低,确保数据库环境的安全与稳定。


参考文档

  • MOS Doc ID 555.1: Oracle Recommended Patches
  • MOS Doc ID 2162547.1: OPlan Patch Planning Tool
  • MOS Doc ID 2246070.1: RU/RUR Patch Naming Convention
  • Oracle Database OPatch User’s Guide

一、问题背景

在企业级数据库管理中,审计是安全合规的核心环节。无论是等保测评、GDPR、还是内部安全审计,数据库审计都扮演着不可替代的角色。然而在 Oracle 12c 之前,传统审计(Traditional Auditing)的实施一直让 DBA 感到头疼。

传统审计的局限性主要体现在以下几个方面:

  • 性能影响大:传统审计依赖大量独立的审计表(如 AUD$FGA_LOG$),每次审计事件触发都需要写入磁盘 I/O,高并发场景下对 OLTP 性能影响显著。根据实践经验,在未优化的情况下,开启全量审计可能导致 5%-15% 的性能下降。尤其是在高频 DML 操作的生产系统上,审计带来的额外 I/O 开销往往成为性能瓶颈。
  • 管理复杂度高:权限审计、操作审计、对象审计分别使用不同的语法和配置方式(AUDIT 语句、AUDIT_TRAIL 参数、DBMS_FGA 包),缺乏统一管理入口,策略分散难以维护。当需要对某类操作添加审计时,DBA 可能需要同时修改多个配置点。
  • 审计数据碎片化:标准审计写入 SYS.AUD$,FGA 审计写入 SYS.FGA_LOG$,OS 审计写入操作系统文件,审计数据分散在多个位置,查询和归档都极为不便。想要获取一份完整的审计报告,需要从多个数据源中整合数据,这在安全事件调查时尤为被动。
  • 灵活性不足:传统审计难以按时间、IP 地址、应用模块等条件进行精细过滤,往往只能开启或关闭某个审计项,无法做到按需审计。

Oracle Unified Auditing(12c 引入) 彻底重构了审计架构,提供统一的策略管理框架,解决了传统审计的诸多痛点。其核心优势包括:

  1. 统一的策略模型:所有审计类型(权限、操作、对象、FGA)通过统一的审计策略(Audit Policy)管理,一条策略即可覆盖多种审计需求,极大简化了管理复杂度。
  2. 内置性能优化:审计记录先写入内存缓冲区(SGA 中的 Unified Audit Queue),再批量持久化到 AUDSYS schema 下的表中,大幅减少磁盘 I/O 开销。相比传统审计,性能影响降低显著。
  3. 灵活的条件过滤:支持按用户、角色、时间、操作系统用户、IP 地址、应用模块等条件精细过滤审计范围,真正做到「该审的审,不该审的不审」。
  4. 集成化的日志管理:通过 DBA_UNIFIED_AUDIT_TRAIL 统一视图查询所有审计数据,支持标准化的清理和归档机制(DBMS_AUDIT_MGMT 包)。

对于有合规审计要求的生产环境,迁移到 Unified Auditing 是必然的选择。本文将从架构原理出发,结合实际项目经验,详细介绍 Unified Auditing 的策略定制、FGA 配置和审计日志管理的最佳实践。


二、理论分析

2.1 Unified Auditing 架构

Unified Auditing 的架构设计围绕三个核心组件展开,理解这些组件的工作原理是正确实施审计策略的前提。

统一审计策略(Unified Audit Policy)

所有审计规则通过 CREATE AUDIT POLICY 语句定义,一条策略可同时包含权限审计、操作审计和对象审计条件。策略创建后处于 DISABLED 状态,需要通过 AUDIT POLICY 语句显式启用。这种「先定义、后启用」的设计使得策略可以预先准备,在变更窗口统一上线。

AUDIT_TRAIL 参数与审计模式

Oracle 提供两种审计模式:Mixed Mode 和 Pure Unified Auditing Mode。

  • Mixed Mode(默认):传统审计和 Unified Auditing 并存。在 Mixed Mode 下,Oracle 自动启用一些默认的统一审计策略(如 ORA_LOGON_FAILURESORA_SECURECONFIG 等),同时传统审计的 AUDIT_TRAIL 参数仍然生效。这是大多数环境的默认状态。
  • Pure Unified Auditing Mode:完全关闭传统审计,所有审计行为统一由 Unified Auditing 管理。需要通过重新链接 Oracle 二进制文件来切换。

在 Mixed Mode 下,可以通过 AUDIT_TRAIL 参数查看传统审计的配置状态:

1
2
3
4
5
SHOW PARAMETER audit_trail;
-- DB : 审计记录写入SYS.AUD$表
-- DB,EXTENDED: 写入表且包含SQL绑定变量
-- OS : 审计记录写入操作系统文件
-- NONE : 关闭传统审计

审计记录存储

Unified Auditing 的审计记录存储在 SYSAUX 表空间的 AUDSYS schema 下,采用内部表结构和自动分区设计。数据写入流程如下:

  1. 审计事件首先写入 SGA 中的 Unified Audit Queue(内存队列)
  2. 后台进程(Unified Auditing Background Writer)定期将队列数据批量刷新到磁盘
  3. 默认每 3 秒或队列达到 1MB 时触发一次刷新
  4. 数据持久化到 AUDSYS schema 下的分区表中

这种异步写入机制是 Unified Auditing 性能优于传统审计的关键所在。

1
2
3
4
5
6
7
8
-- 查看审计队列配置
SELECT * FROM V$UNIFIED_AUDIT_QUEUE_WRITERS;

-- 查看SYSAUX中审计占用空间
SELECT occupant_name, space_usage_kbytes,
ROUND(space_usage_kbytes/1024, 2) AS usage_mb
FROM V$SYSAUX_OCCUPANTS
WHERE occupant_name LIKE '%AUDIT%';

2.2 审计策略类型

Unified Auditing 支持以下四种审计策略类型,可以在一条策略中混合使用,这是其灵活性的核心体现:

类型 说明 适用场景
权限审计(Privilege Audit) 审计系统权限的使用,无论用户是否真正拥有该权限 监控特权操作,如 DROP ANY TABLE
操作审计(Action Audit) 审计 DDL/DML 操作,按操作类型审计 监控特定表的增删改查
对象审计(Object Audit) 审计特定对象上的操作 精确控制到表/视图级别的审计
细粒度审计(FGA) 基于条件的精细审计,支持 WHERE 条件过滤 按业务逻辑条件审计,如薪资超过某阈值

实际项目中,最常用的是操作审计和 FGA 的组合:操作审计负责记录所有对核心表的变更操作,FGA 负责在特定业务条件下触发更详细的审计记录。

2.3 审计日志管理

查询审计日志

统一通过 DBA_UNIFIED_AUDIT_TRAIL 视图查询,该视图整合了所有类型的审计记录:

1
2
3
4
5
SELECT event_timestamp, dbusername, action_name,
object_schema, object_name, sql_text
FROM DBA_UNIFIED_AUDIT_TRAIL
ORDER BY event_timestamp DESC
FETCH FIRST 100 ROWS ONLY;

审计日志清理策略

审计日志不能无限制增长,否则将迅速填满 SYSAUX 表空间,影响数据库正常运行。建议按以下策略管理:

  • 保留周期:根据合规要求设定(通常 90 天至 1 年),等保三级通常要求至少 6 个月
  • 清理方式:使用 DBMS_AUDIT_MGMT 包进行标准化清理,这是 Oracle 官方推荐的方式
  • 归档方式:清理前先导出到独立表空间或外部存储,确保历史数据可追溯

审计日志导出

可通过 Data Pump 或 CTAS 方式导出审计数据。建议使用 CTAS 导出到独立表空间,便于后续查询和归档管理:

1
2
3
4
5
-- 导出到独立表(建议使用独立表空间)
CREATE TABLE aud_archive.unified_audit_202606
TABLESPACE aud_archive_ts
AS SELECT * FROM UNIFIED_AUDIT_TRAIL
WHERE event_timestamp < SYSTIMESTAMP - INTERVAL '90' DAY;

三、实战操作

3.1 启用 Unified Auditing

检查当前审计模式

1
2
3
4
5
6
7
-- 检查Unified Auditing是否已编译启用
SELECT VALUE FROM V$OPTION WHERE PARAMETER = 'Unified Auditing';
-- TRUE 表示已编译启用(12c及以上默认为TRUE)

-- 检查当前审计模式
SELECT PARAMETER, VALUE FROM V$OPTION
WHERE PARAMETER = 'Unified Auditing';

迁移到纯 Unified Auditing 模式

Oracle 12c 默认是 Mixed Mode,如需完全迁移到纯 Unified Auditing 模式(关闭传统审计),需要关闭数据库并执行以下步骤:

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 关闭数据库
sqlplus / as sysdba
SHUTDOWN IMMEDIATE;
EXIT;

# 2. 重新链接Oracle二进制文件,启用纯Unified Auditing
cd $ORACLE_HOME/rdbms/lib
make -f ins_rdbms.mk uniaud_on ioracle

# 3. 启动数据库
sqlplus / as sysdba
STARTUP;

注意:切换到纯 Unified Auditing 模式是不可逆操作(需要重新安装数据库才能回退)。如果当前使用 Mixed Mode 能满足需求,建议保持 Mixed Mode。大多数环境下 Mixed Mode 已经足够。

验证启用状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 验证Unified Auditing模式
SELECT PARAMETER, VALUE
FROM V$OPTION
WHERE PARAMETER = 'Unified Auditing';

-- 查看已有的审计策略(包括默认策略)
SELECT POLICY_NAME, ENABLED_OPT, USER_AUDIT_OPTION
FROM AUDIT_UNIFIED_POLICIES
ORDER BY POLICY_NAME;

-- 查看已启用的策略
SELECT POLICY_NAME, ENABLED_OPTION, ENTITY_NAME, ENTITY_TYPE
FROM AUDIT_UNIFIED_ENABLED_POLICIES;

-- 查看默认策略的具体审计项
SELECT POLICY_NAME, AUDIT_OPTION, AUDIT_OPTION_TYPE, OBJECT_SCHEMA, OBJECT_NAME
FROM AUDIT_UNIFIED_POLICIES
WHERE POLICY_NAME = 'ORA_SECURECONFIG'
ORDER BY AUDIT_OPTION;

3.2 创建审计策略

权限审计策略

审计所有使用 DROP ANY TABLE 权限的操作,这是安全合规中最基本的要求之一:

1
2
3
4
5
6
7
8
9
10
11
12
-- 创建权限审计策略
CREATE AUDIT POLICY priv_drop_any_table_policy
PRIVILEGES DROP ANY TABLE;

-- 启用策略(对所有用户生效)
AUDIT POLICY priv_drop_any_table_policy;

-- 仅对特定用户启用
AUDIT POLICY priv_drop_any_table_policy BY scott, hr;

-- 仅对非DBA用户启用(排除SYS和SYSDBA连接)
AUDIT POLICY priv_drop_any_table_policy EXCEPT SYS;

DML 操作审计策略

审计 HR 模式下核心人事表的所有 DML 操作,这类审计在等保测评中经常被要求:

1
2
3
4
5
6
7
8
9
10
11
-- 创建DML审计策略
CREATE AUDIT POLICY hr_dml_audit_policy
ACTIONS INSERT ON HR.EMPLOYEES,
UPDATE ON HR.EMPLOYEES,
DELETE ON HR.EMPLOYEES,
INSERT ON HR.DEPARTMENTS,
UPDATE ON HR.DEPARTMENTS,
DELETE ON HR.DEPARTMENTS;

-- 启用策略(排除SYS用户)
AUDIT POLICY hr_dml_audit_policy BY ALL EXCEPT SYS;

登录失败审计

Oracle 默认已启用 ORA_LOGON_FAILURES 策略,但可以自定义更精细的登录审计策略,用于检测暴力破解和异常登录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- 创建登录审计策略
CREATE AUDIT POLICY login_audit_policy
ACTIONS LOGON, LOGOFF,
ROLES DBA, DATAPUMP_EXP_FULL_DATABASE;

-- 启用
AUDIT POLICY login_audit_policy;

-- 查询最近7天的登录失败记录
SELECT event_timestamp, dbusername, client_identifier,
action_name, return_code, userhost,
terminal, authentication_type
FROM DBA_UNIFIED_AUDIT_TRAIL
WHERE action_name IN ('LOGON', 'LOGOFF')
AND return_code != 0
AND event_timestamp > SYSTIMESTAMP - INTERVAL '7' DAY
ORDER BY event_timestamp DESC;

-- 统计失败登录的用户分布
SELECT dbusername, COUNT(*) AS fail_count
FROM DBA_UNIFIED_AUDIT_TRAIL
WHERE action_name = 'LOGON'
AND return_code != 0
AND event_timestamp > SYSTIMESTAMP - INTERVAL '7' DAY
GROUP BY dbusername
ORDER BY fail_count DESC;

特权操作审计

审计所有 DBA 角色用户的敏感操作,防止特权滥用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 创建特权操作审计策略
CREATE AUDIT POLICY dba_operations_policy
PRIVILEGES ALTER SYSTEM,
ALTER DATABASE,
ALTER USER,
DROP USER,
CREATE ANY TABLE,
ALTER ANY TABLE,
DROP ANY TABLE
WHEN 'SYS_CONTEXT(''USERENV'',''SESSION_USER'') NOT IN (''SYS'',''SYSTEM'')'
EVALUATE PER SESSION;

-- 启用
AUDIT POLICY dba_operations_policy BY USERS WITH GRANTED ROLES DBA;

策略创建完成后,可以查看策略的详细定义:

1
2
3
4
5
-- 查看策略详情
SELECT POLICY_NAME, AUDIT_OPTION, AUDIT_OPTION_TYPE,
OBJECT_SCHEMA, OBJECT_NAME, COMMON
FROM AUDIT_UNIFIED_POLICIES
WHERE POLICY_NAME = 'DBA_OPERATIONS_POLICY';

3.3 FGA 配置

细粒度审计(FGA)通过 DBMS_FGA 包实现,是 Unified Auditing 中最灵活的审计方式。与普通操作审计不同,FGA 支持基于 WHERE 条件精确控制审计范围,适合对敏感数据的按条件审计。

FGA 策略创建

场景一:审计对薪资敏感字段的 SELECT 访问

当有人查询高薪员工的薪资信息时触发审计,这是金融和人力资源场景中的典型需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BEGIN
DBMS_FGA.ADD_POLICY(
object_schema => 'HR',
object_name => 'EMPLOYEES',
policy_name => 'AUDIT_SALARY_ACCESS',
audit_column => 'SALARY,COMMISSION_PCT',
audit_condition => 'SALARY > 10000',
audit_column_opts => DBMS_FGA.ANY_COLUMNS,
statement_types => 'SELECT',
handler_schema => NULL,
handler_module => NULL,
enable => TRUE
);
END;
/

参数说明:

  • audit_column:指定需要审计的列,多列用逗号分隔
  • audit_condition:触发审计的条件,类似 WHERE 子句
  • audit_column_optsANY_COLUMNS 表示访问任一指定列即触发;ALL_COLUMNS 表示访问所有指定列才触发
  • statement_types:审计的 SQL 操作类型,支持 SELECTINSERTUPDATEDELETE

场景二:审计对财务交易表的 DELETE 操作

删除财务交易记录是高风险操作,应无条件审计:

1
2
3
4
5
6
7
8
9
10
11
BEGIN
DBMS_FGA.ADD_POLICY(
object_schema => 'FINANCE',
object_name => 'TRANSACTIONS',
policy_name => 'AUDIT_FINANCE_DELETE',
audit_condition => '1=1',
statement_types => 'DELETE',
enable => TRUE
);
END;
/

场景三:审计对客户信息的批量访问

当查询涉及客户表的全部列(可能意味着批量数据导出)时触发审计:

1
2
3
4
5
6
7
8
9
10
11
12
BEGIN
DBMS_FGA.ADD_POLICY(
object_schema => 'CRM',
object_name => 'CUSTOMERS',
policy_name => 'AUDIT_BULK_CUSTOMER_ACCESS',
audit_condition => '1=1',
statement_types => 'SELECT',
audit_column_opts => DBMS_FGA.ALL_COLUMNS,
enable => TRUE
);
END;
/

FGA 策略管理

FGA 策略支持动态启用、禁用和删除,无需重建策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 启用FGA策略
BEGIN
DBMS_FGA.ENABLE_POLICY('HR', 'EMPLOYEES', 'AUDIT_SALARY_ACCESS');
END;
/

-- 禁用FGA策略
BEGIN
DBMS_FGA.DISABLE_POLICY('HR', 'EMPLOYEES', 'AUDIT_SALARY_ACCESS');
END;
/

-- 删除FGA策略
BEGIN
DBMS_FGA.DROP_POLICY('HR', 'EMPLOYEES', 'AUDIT_SALARY_ACCESS');
END;
/

-- 查看已有FGA策略
SELECT object_schema, object_name, policy_name,
enabled, sel, ins, upd, del,
audit_column, audit_condition
FROM DBA_AUDIT_POLICES;

FGA 日志查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- FGA审计日志在Unified Auditing模式下统一查询
SELECT event_timestamp, dbusername, object_schema,
object_name, sql_text, sql_binds
FROM DBA_UNIFIED_AUDIT_TRAIL
WHERE audit_type = 'FGA AUDIT'
AND event_timestamp > SYSTIMESTAMP - INTERVAL '1' DAY
ORDER BY event_timestamp DESC;

-- 按对象统计FGA触发次数
SELECT object_schema, object_name, COUNT(*) AS trigger_count
FROM DBA_UNIFIED_AUDIT_TRAIL
WHERE audit_type = 'FGA AUDIT'
AND event_timestamp > SYSTIMESTAMP - INTERVAL '7' DAY
GROUP BY object_schema, object_name
ORDER BY trigger_count DESC;

3.4 审计日志维护

审计日志维护是生产环境中最容易被忽视,却又最容易出问题的环节。没有及时清理的审计日志会迅速填满 SYSAUX 表空间,导致数据库异常。

初始化审计清理框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 初始化审计清理配置(只需执行一次)
BEGIN
DBMS_AUDIT_MGMT.INIT_CLEANUP(
audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED,
default_cleanup_interval => 720 -- 720分钟=12小时检查一次
);
END;
/

-- 设置审计记录保留天数(90天)
BEGIN
DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP(
audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED,
last_archive_time => SYSTIMESTAMP - INTERVAL '90' DAY
);
END;
/

日志清理脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 手动执行清理(清除超过保留期的审计记录)
BEGIN
DBMS_AUDIT_MGMT.CLEAN_AUDIT_TRAIL(
audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED,
use_last_arch_timestamp => TRUE
);
END;
/

-- 设置自动清理Job(推荐,每日自动清理)
BEGIN
DBMS_AUDIT_MGMT.CREATE_PURGE_JOB(
audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED,
audit_trail_purge_interval => 24, -- 每24小时执行一次
audit_trail_purge_name => 'UNIFIED_AUDIT_PURGE_JOB',
use_last_arch_timestamp => TRUE
);
END;
/

-- 查看清理Job状态
SELECT JOB_NAME, JOB_STATUS, JOB_FREQUENCY
FROM DBA_AUDIT_MGMT_CLEANUP_JOBS;

归档策略

建议在清理前先归档到独立表空间,保留历史数据以满足合规留存要求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- 创建归档表空间(建议放在独立磁盘组上)
CREATE TABLESPACE aud_archive_ts
DATAFILE '+DATA' SIZE 10G AUTOEXTEND ON NEXT 1G MAXSIZE 50G
EXTENT MANAGEMENT LOCAL AUTOALLOCATE;

-- 创建归档用户
CREATE USER aud_archive IDENTIFIED BY "StrongPass123!"
DEFAULT TABLESPACE aud_archive_ts
QUOTA UNLIMITED ON aud_archive_ts;

GRANT CREATE TABLE TO aud_archive;

-- 按月归档(以2026年5月为例)
CREATE TABLE aud_archive.audit_202605
TABLESPACE aud_archive_ts
AS SELECT * FROM SYS.UNIFIED_AUDIT_TRAIL
WHERE event_timestamp >= DATE '2026-05-01'
AND event_timestamp < DATE '2026-06-01';

-- 归档完成后,设置归档时间戳,允许清理该时段及之前的数据
BEGIN
DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP(
audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED,
last_archive_time => TO_TIMESTAMP('2026-06-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
);
END;
/

性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 调整审计队列大小(高并发场景下建议增大)
-- 默认1MB,可根据审计量适当增大到4MB
ALTER SYSTEM SET unified_audit_sga_queue_size = 4194304 SCOPE=BOTH;

-- 查询当前队列参数
SELECT name, value FROM V$PARAMETER
WHERE name LIKE '%unified_audit%';

-- 监控队列写入延迟
SELECT event, total_waits, time_waited_micro,
ROUND(time_waited_micro/NULLIF(total_waits,0), 2) AS avg_wait_us
FROM V$SYSTEM_EVENT
WHERE event LIKE '%audit%'
ORDER BY time_waited_micro DESC;

四、结果验证

审计策略生效验证

策略上线后,需要进行端到端验证,确认审计策略确实生效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 查看当前已启用的审计策略
SELECT POLICY_NAME, ENABLED_OPTION, ENTITY_NAME, ENTITY_TYPE
FROM AUDIT_UNIFIED_ENABLED_POLICIES;

-- 测试:以scott用户执行被审计的操作
-- SQL> CONN scott/tiger
-- SQL> DELETE FROM hr.employees WHERE employee_id = 999;
-- SQL> COMMIT;

-- 检查审计日志是否记录了该操作
SELECT event_timestamp, dbusername, action_name,
object_schema, object_name, sql_text
FROM DBA_UNIFIED_AUDIT_TRAIL
WHERE dbusername = 'SCOTT'
AND action_name = 'DELETE'
AND event_timestamp > SYSTIMESTAMP - INTERVAL '1' HOUR
ORDER BY event_timestamp DESC;

审计日志查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- 综合审计报表:按操作类型统计最近7天的审计事件
SELECT action_name, COUNT(*) AS cnt
FROM DBA_UNIFIED_AUDIT_TRAIL
WHERE event_timestamp > SYSTIMESTAMP - INTERVAL '7' DAY
GROUP BY action_name
ORDER BY cnt DESC;

-- 安全事件筛查:权限变更和用户管理操作
SELECT event_timestamp, dbusername, action_name,
object_schema, object_name, sql_text
FROM DBA_UNIFIED_AUDIT_TRAIL
WHERE action_name IN ('ALTER USER', 'GRANT', 'REVOKE',
'DROP USER', 'CREATE USER')
AND event_timestamp > SYSTIMESTAMP - INTERVAL '30' DAY
ORDER BY event_timestamp DESC;

-- 失败操作审计:重点关注权限不足的尝试
SELECT event_timestamp, dbusername, action_name,
return_code, sql_text
FROM DBA_UNIFIED_AUDIT_TRAIL
WHERE return_code != 0
AND event_timestamp > SYSTIMESTAMP - INTERVAL '7' DAY
ORDER BY event_timestamp DESC;

-- 按用户统计操作频次(识别高频操作用户)
SELECT dbusername, COUNT(*) AS operation_count
FROM DBA_UNIFIED_AUDIT_TRAIL
WHERE event_timestamp > SYSTIMESTAMP - INTERVAL '1' DAY
GROUP BY dbusername
ORDER BY operation_count DESC
FETCH FIRST 20 ROWS ONLY;

性能影响评估

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 审计相关的等待事件监控
SELECT event, total_waits, time_waited_micro,
ROUND(time_waited_micro/NULLIF(total_waits,0), 2) AS avg_wait_us
FROM V$SYSTEM_EVENT
WHERE event LIKE '%audit%'
OR event LIKE '%UL%'
ORDER BY time_waited_micro DESC;

-- 审计占用的SYSAUX空间
SELECT ROUND(space_usage_kbytes/1024, 2) AS usage_mb
FROM V$SYSAUX_OCCUPANTS
WHERE occupant_name = 'AUDITSYS';

-- 建议结合AWR报告评估:比较启用审计前后的DB Time变化
-- 关注以下指标是否有显著变化:
-- - db file sequential read
-- - log file sync
-- - enq: UL - contention

五、经验总结

审计策略设计原则

在多个大型项目的实施过程中,总结出以下审计策略设计原则:

  1. 最小化原则:只审计必要的操作和对象,避免无差别全量审计。审计范围越大,性能影响和存储消耗越高。一个常见的误区是「审计越多越安全」,实际上过多的审计日志反而会淹没真正有价值的安全事件。
  2. 分层策略:将审计策略按重要性分层设计。核心资产(如客户数据表、财务表、权限表)使用精细审计策略,一般操作使用粗粒度审计策略,内部测试环境可以降低审计级别。
  3. 角色关联:通过 BY USERS WITH GRANTED ROLES 按角色范围启用策略,避免逐用户配置的管理负担。随着用户数量增加,角色关联策略的维护成本远低于用户关联策略。
  4. 定期评审:每季度评审审计策略的有效性和覆盖面,移除不再需要的策略,新增新业务场景的审计需求。审计策略应该是动态演进的,而非一成不变。
  5. 变更管理:审计策略的变更应纳入变更管理流程,变更前做好影响评估,变更后进行端到端验证。

性能影响最小化

根据实际项目经验,以下措施可以有效降低 Unified Auditing 的性能影响:

  • 避免在高并发 OLTP 核心路径上开启精细审计:如需审计高频 DML 操作,优先选择 Actions 级别审计而非 FGA。Actions 审计的开销通常低于 FGA 的条件评估开销。
  • 使用 EXCEPT 排除高频系统账户:如 AUDIT POLICY xxx EXCEPT SYS,避免审计系统内部操作产生大量无效审计记录。
  • 监控审计队列延迟:如果 V$UNIFIED_AUDIT_QUEUE_WRITERS 显示队列积压严重,说明审计写入速度跟不上事件产生速度,需增大 SGA 队列尺寸或优化审计范围。
  • 审计日志独立表空间:将归档审计表放在独立磁盘组上,避免审计归档操作影响业务表空间的 I/O 性能。

日志管理最佳实践

  1. 在生产启用审计前,先配置好清理框架DBMS_AUDIT_MGMT.INIT_CLEANUP 和清理 Job 必须提前就位。这是最重要的一条经验——没有配置清理的审计日志会在数月内填满 SYSAUX 表空间,届时的紧急处理将非常被动。
  2. 归档优于直接清理:建议采用「归档到独立表空间 → 设置归档时间戳 → 触发清理」的三步流程。直接清理而不归档,在合规审查时可能无法提供历史审计数据。
  3. 定期监控 SYSAUX 空间:审计数据是 SYSAUX 空间增长的主要因素之一。建议设置表空间使用率告警(80% 预警,90% 告警),及时发现审计日志异常增长。
  4. 备份审计归档表:归档数据建议纳入 RMAN 备份范围,满足合规留存要求。部分行业法规要求审计数据保存 3-5 年。
  5. 保留清理日志:审计清理操作本身也应被审计。Oracle 默认会记录 DBMS_AUDIT_MGMT 的操作,确保清理过程可追溯,满足「谁在什么时间清理了什么审计数据」的审计要求。

本文涉及的 SQL 均在 Oracle 19c 环境下验证。不同版本在 DBMS_AUDIT_MGMT 包的参数细节上可能存在差异,建议参考对应版本的官方文档。对于 Oracle 12c(12.1)用户,部分语法可能略有不同,请以官方文档为准。

在日常运维中,你是否见过这样的场景:开发人员拿到 DBA 角色”方便调试”,应用账号用 SYSDBA 连接数据库,备份账号拥有 CREATE ANY TABLE 权限……这些看似”省事”的做法,实则是数据库安全的定时炸弹。本文将从理论到实战,系统讲解如何在 Oracle 数据库中践行最小权限原则,设计合理的角色体系。

阅读全文 »

一、问题背景

在 Oracle DBA 的日常运维中,网络连接问题看似简单,实则排查链条极长。客户端报一个 ORA-12170: TNS:Connect timeout occurred,背后可能是 Listener 没启动、防火墙拦截了端口、ACL 策略阻断了流量,甚至是数据库实例没有注册到 Listener。

TNS-12541 和 ORA-12170 是生产环境中最高频的两类网络错误。前者意味着客户端根本无法与 Listener 建立 TCP 连接,后者意味着连接建立后在握手阶段超时。两者的根因可能完全不同,需要系统化的诊断方法论。

本文将从 Oracle 网络架构原理出发,结合实战诊断命令,提供一套完整的 TNS 网络故障排查指南。


二、理论分析

2.1 Oracle 网络架构

Oracle 的连接模型是经典的三层架构:

1
Client (OCI/JDBC) --> Listener --> Server Process --> Database Instance

连接建立流程:

  1. Client 通过 tnsnames.ora 或 Easy Connect 解析出 Listener 地址和端口
  2. Client 向 Listener 发起 TCP 连接
  3. Listener 验证 Service Name,分配 Server Process
  4. Client 与 Server Process 建立独立连接(此时 Listener 不再参与)
  5. Server Process 与 Database Instance 交互

Dedicated Server vs Shared Server:

  • Dedicated Server:每个客户端连接对应一个独立的 Server Process,资源消耗大但隔离性好
  • Shared Server:通过 Dispatcher 共享 Server Process,适合高并发短连接场景

生产环境绝大多数使用 Dedicated Server 模式。

Listener 注册机制:

  • 静态注册:在 listener.ora 中显式配置 SID_LIST_LISTENER,无论实例是否启动都能接受连接
  • 动态注册:实例启动后自动向 Listener 注册(通过 PMON 进程),默认端口 1521

动态注册的优势是无需手动维护 listener.ora,劣势是实例未启动时 Listener 不知道该 Service。这也是 ORA-12514 的常见根因。

2.2 TNS 连接流程

TNS 解析过程:

1
2
3
4
1. 检查 sqlnet.ora 中的 NAMES.DIRECTORY_PATH
2. 按顺序尝试:TNSNAMES -> LDAP -> EZCONNECT
3. 从 tnsnames.ora 解析出 HOST/PORT/SERVICE_NAME
4. 建立 TCP 连接到指定的 HOST:PORT

连接超时机制:

Oracle 网络连接涉及多个超时参数:

参数 位置 作用
TCP.CONNECT_TIMEOUT sqlnet.ora TCP 连接建立超时(默认无限制)
SQLNET.INBOUND_CONNECT_TIMEOUT sqlnet.ora 入站连接握手超时(默认 60s)
SQLNET.RECV_TIMEOUT sqlnet.ora 接收数据超时
SQLNET.SEND_TIMEOUT sqlnet.ora 发送数据超时

Dead Connection Detection (DCD):

通过 SQLNET.EXPIRE_TIME 参数,Server 端定期向 Client 发送探测包。如果 Client 已断开(异常退出),Server 端能及时释放资源。这个参数在网络层面也起到 Keep-Alive 的作用,是对抗防火墙 Idle Timeout 的关键武器。

2.3 常见网络错误

TNS-12541: TNS:no listener

Client 无法与 Listener 建立 TCP 连接。根因可能是:

  • Listener 进程未启动
  • Listener 监听的端口不是 1521(或 tnsnames.ora 中配置的端口)
  • 防火墙阻断了 Client 到 Listener 端口的流量
  • 主机名解析错误(DNS 或 /etc/hosts 配置问题)

ORA-12170: TNS:Connect timeout occurred

TCP 连接已建立,但在 TNS 握手阶段超时。根因可能是:

  • 防火墙在 TCP 连接建立后阻断了 TNS 协议数据
  • SQLNET.INBOUND_CONNECT_TIMEOUT 设置过短
  • Server 端资源不足,无法及时处理连接请求
  • 防火墙的 Deep Packet Inspection 干扰了 TNS 协议

ORA-12514: TNS:listener does not currently know of service

Client 成功连接到 Listener,但 Listener 找不到请求的 Service。根因通常是:

  • 数据库实例未启动
  • 动态注册未完成(PMON 还没注册)
  • tnsnames.ora 中的 SERVICE_NAME 拼写错误
  • RAC 环境中 Service 未在所有节点注册

ORA-12547: TNS:lost contact

连接建立后突然断开。根因可能是:

  • Server Process 异常崩溃
  • 操作系统资源限制(ulimit、进程数限制)
  • 网络链路中断
  • 防火墙主动断开连接

2.4 防火墙影响

防火墙是 TNS 网络故障的头号杀手。主要影响体现在三个方面:

Idle Timeout: 防火墙通常对空闲连接有超时策略(默认可能 300-600 秒)。如果应用连接池中的连接长时间不用,防火墙会悄悄断开,但 Client 和 Server 可能并不知道,下次使用时就会报错。

解决方案: 设置 SQLNET.EXPIRE_TIME,让探测包的发送间隔小于防火墙的 Idle Timeout。

ACL 策略: 企业网络中的 ACL 可能只允许特定端口和源 IP 的流量。Oracle 默认使用 1521 端口,但 RAC 环境会使用其他端口。

Deep Packet Inspection: 某些防火墙会解析 TNS 协议内容,可能干扰正常通信。


三、实战操作

3.1 Listener 诊断

完整诊断命令序列:

1
2
3
4
5
6
7
8
9
10
11
# 1. 检查 Listener 进程是否存在
ps -ef | grep tnslsnr

# 2. 查看 Listener 完整状态
lsnrctl status

# 3. 查看 Listener 服务列表
lsnrctl services

# 4. 查看 Listener 日志
tail -100 $ORACLE_HOME/network/log/listener.log

lsnrctl status 关键信息解读:

1
2
3
4
5
Listening Endpoints Summary...
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=dbserver)(PORT=1521)))
Services Summary...
Service "ORCL" has 1 instance(s).
Instance "ORCL", status READY, has 1 handler(s) for this service...
  • status READY 表示动态注册成功
  • status BLOCKED 表示实例未就绪或静态注册
  • has 1 handler(s) 表示有可用的连接处理器

listener.ora 标准配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 动态注册(推荐,listener.ora 可以为空或不存在)
-- 仅需确保实例的 local_listener 参数指向正确地址

-- 静态注册(用于需要在实例启动前连接的场景)
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = dbserver)(PORT = 1521))
)
)

SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = ORCL)
(ORACLE_HOME = /u01/app/oracle/product/19c/dbhome_1)
(SID_NAME = ORCL)
)
)

动态注册配置:

1
2
3
4
5
-- 在数据库中设置 local_listener
ALTER SYSTEM SET local_listener = '(ADDRESS=(PROTOCOL=TCP)(HOST=dbserver)(PORT=1521))' SCOPE=BOTH;

-- 手动触发注册
ALTER SYSTEM REGISTER;

3.2 TNS 配置

tnsnames.ora 标准配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ORCL =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = dbserver)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME = ORCL)
)
)

-- RAC 环境配置(含故障转移)
ORCL_RAC =
(DESCRIPTION =
(ADDRESS_LIST =
(LOAD_BALANCE = ON)
(FAILOVER = ON)
(ADDRESS = (PROTOCOL = TCP)(HOST = node1-vip)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = node2-vip)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME = ORCL)
(FAILOVER_MODE =
(TYPE = SELECT)
(METHOD = BASIC)
(RETRIES = 3)
(DELAY = 5)
)
)
)

sqlnet.ora 关键参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- TNS 解析顺序
NAMES.DIRECTORY_PATH= (TNSNAMES, EZCONNECT)

-- 连接超时控制
TCP.CONNECT_TIMEOUT=10
SQLNET.INBOUND_CONNECT_TIMEOUT=60
SQLNET.RECV_TIMEOUT=300
SQLNET.SEND_TIMEOUT=300

-- Dead Connection Detection(关键!)
SQLNET.EXPIRE_TIME=10

-- 日志级别(排查时开启)
DIAG_ADR_ENABLED=OFF
TRACE_LEVEL_CLIENT=16
TRACE_LEVEL_SERVER=16
TRACE_DIRECTORY_CLIENT=/tmp/sqlnet_trace
TRACE_DIRECTORY_SERVER=/u01/app/oracle/network/trace

Easy Connect 命名:

tnsnames.ora 配置有问题时,可以直接使用 Easy Connect 语法:

1
2
3
4
5
6
7
8
-- 标准格式
sqlplus user/pass@dbserver:1521/ORCL

-- 指定实例
sqlplus user/pass@dbserver:1521/ORCL:DEDICATED

-- JDBC 连接串
jdbc:oracle:thin:@dbserver:1521/ORCL

3.3 网络诊断工具

tnsping 命令:

1
2
3
4
5
6
7
8
# 基本连通性测试
tnsping ORCL

# 测试 Easy Connect
tnsping dbserver:1521/ORCL

# 指定次数
tnsping ORCL 5

tnsping 测试的是 Client 到 Listener 的可达性,不验证数据库是否可用。OK 表示 Listener 响应了 TNS 协议。

netstat/ss 连接状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看 Listener 端口监听状态
ss -tlnp | grep 1521

# 查看 Oracle 相关连接
ss -tnp | grep oracle

# 查看连接状态统计
ss -s

# 排查 TIME_WAIT 积压
ss -tn state time-wait | wc -l

# 查看 ESTABLISHED 连接数
ss -tn state established | grep 1521 | wc -l

tcpdump 网络抓包:

1
2
3
4
5
6
7
8
9
10
11
# 抓取 1521 端口的流量
tcpdump -i eth0 port 1521 -w /tmp/oracle_tns.pcap

# 抓取特定客户端的流量
tcpdump -i eth0 src host 192.168.1.100 and port 1521 -w /tmp/client.pcap

# 实时查看 TNS 连接(不写文件)
tcpdump -i eth0 port 1521 -nn -c 100

# 抓取 RAC 环境的 Interconnect 流量
tcpdump -i eth1 port 1521 or port 1522

Oracle Net Manager:

图形化工具,可用于配置和测试网络连接:

1
2
# 启动 Oracle Net Manager
netmgr

3.4 防火墙配置

SQLNET.EXPIRE_TIME 配置:

这是对抗防火墙 Idle Timeout 最重要的参数。原理是 Server 端定期发送探测包(TNS Dead Connection Detection probe),保持连接活跃。

1
2
3
4
5
-- 在 sqlnet.ora 中配置
SQLNET.EXPIRE_TIME=10

-- 原则:SQLNET.EXPIRE_TIME * 60 < 防火墙 Idle Timeout
-- 例如防火墙 Idle Timeout 为 600 秒,则 EXPIRE_TIME 应设为 <= 9

重要提示: SQLNET.EXPIRE_TIME 需要在 Server 端的 sqlnet.ora 中配置,修改后需要重启 Listener 和所有数据库连接才能生效。

防火墙端口规划:

组件 默认端口 说明
Listener 1521 必须开放
OEM 1158/5500 Oracle Enterprise Manager
DBConsole 5500 12c+ EM Express
RAC VIP 与 Listener 相同 每个节点一个 VIP
RAC Interconnect 1521-1525 私有网络,不需要开放给客户端
SCAN Listener 1521 RAC SCAN 地址

RAC 环境特殊端口:

1
2
3
4
5
6
7
8
9
# 查看 RAC 相关端口
srvctl config listener
srvctl config scan_listener

# RAC 环境必须开放的端口
# - 每个节点的 Listener 端口(通常都是 1521)
# - 每个节点的 VIP 地址
# - SCAN IP 地址
# - ASM 端口(如使用 ASM)

四、结果验证

完成排查和修复后,需要系统性地验证各项指标。

连接测试:

1
2
3
4
5
6
7
8
9
10
11
# 1. tnsping 测试
tnsping ORCL

# 2. SQL*Plus 连接测试
sqlplus system/pass@ORCL

# 3. Easy Connect 测试
sqlplus system/pass@dbserver:1521/ORCL

# 4. JDBC 连接测试(Java 应用场景)
java -cp ojdbc11.jar:. TestConnection dbserver 1521 ORCL

Listener 状态检查:

1
2
3
4
5
6
7
8
# 确认 Listener 状态正常
lsnrctl status

# 确认所有 Service 已注册
lsnrctl services

# 检查 Listener 日志无异常
tail -50 $ORACLE_HOME/network/log/listener.log

网络延迟测试:

1
2
3
4
5
6
7
8
# 基本延迟测试
ping -c 10 dbserver

# TCP 端口连通性
nc -zv dbserver 1521

# 网络质量测试(如有 mtr)
mtr -r -c 100 dbserver

连接池验证(应用端):

1
2
3
4
# 应用连接池应配置以下参数
# - validateConnectionOnBorrow=true
# - validationQuery="SELECT 1 FROM DUAL"
# - maxIdleTime < 防火墙 Idle Timeout

五、经验总结

网络问题的标准排查流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1. 确认错误信息
├── TNS-12541 → 检查 Listener 是否启动
├── ORA-12170 → 检查防火墙/网络延迟
├── ORA-12514 → 检查 Service 注册
└── ORA-12547 → 检查 Server Process/资源限制

2. 网络层排查
├── ping 测试(基础连通性)
├── telnet/nc 测试端口
└── tcpdump 抓包分析

3. Listener 层排查
├── lsnrctl status/services
├── listener.log 日志分析
└── listener.ora 配置检查

4. 数据库层排查
├── 实例状态检查
├── Service 注册检查
└── alert.log 日志分析

5. 防火墙层排查
├── SQLNET.EXPIRE_TIME 配置
├── ACL 策略确认
└── 防火墙日志分析

防火墙配置 Checklist

  • SQLNET.EXPIRE_TIME 已配置且小于防火墙 Idle Timeout
  • 所有节点的 Listener 端口已开放
  • RAC 环境的 VIP 和 SCAN IP 已开放
  • 防火墙未启用对 TNS 协议的 Deep Packet Inspection
  • ACL 策略允许双向流量
  • 连接池配置了连接验证机制

RAC 网络特殊注意事项

  1. VIP 漂移:RAC 的 VIP 在节点故障时会漂移到其他节点,防火墙必须允许目标节点接收 VIP 流量
  2. SCAN Listener:11gR2+ 使用 SCAN(Single Client Access Name),确保 SCAN IP 和 SCAN Listener 端口可达
  3. Interconnect:节点间通信使用独立网络,不要让防火墙干扰 Interconnect 流量
  4. Service 分布:确认 Service 在所有预期节点上都已注册,使用 srvctl status service 检查

作为 OCM 认证 DBA,我的经验是:90% 的 TNS 网络问题都与防火墙有关。在排查网络故障时,第一时间确认 SQLNET.EXPIRE_TIME 和防火墙 Idle Timeout 的关系,往往能快速定位根因。