一、背景与需求
在 Oracle 数据库日常运维中,参数管理是一个经常容易被忽略但又非常关键的工作。很多时候,我们会通过 ALTER SYSTEM 修改数据库参数,例如:
sql
ALTER SYSTEM SET open_cursors = 1000 SCOPE = SPFILE;
ALTER SYSTEM SET sga_target = 16G SCOPE = SPFILE;
ALTER SYSTEM SET db_file_name_convert = '/u01/old/', '+DATA/new/' SCOPE = SPFILE;
如果参数修改使用的是 SCOPE = SPFILE,那么参数只会被写入 SPFILE,并不会立即在内存中生效。只有在数据库重启之后,新的参数值才会真正加载到内存中。
因此,在巡检或者变更验证时,我们经常会有这样一个需求:
检查哪些参数已经在 SPFILE 中被修改,但当前内存中的参数值仍然和 SPFILE 不一致。
这类检查在以下场景中非常有用:
- 数据库参数变更后,确认是否存在未重启导致未生效的参数。
- 巡检 RAC 环境中不同实例的参数是否一致。
- 检查
*.参数与实例名.参数的实例级配置差异。 - 排查 SPFILE 配置和实际运行参数不一致的问题。
本文提供一个适用于 Oracle 11g 到 Oracle 19c 的巡检 SQL,并对脚本逻辑进行详细说明。
二、涉及的主要视图
这个脚本主要依赖以下几个 Oracle 动态性能视图。
1. gv$instance
gv$instance 用于获取 RAC 环境中的实例信息。
常用字段包括:
text
inst_id 实例 ID
instance_name 实例名
在单实例环境下,gv$instance 通常只有一行数据;在 RAC 环境下,会有多个实例记录。
2. v$spparameter
v$spparameter 用于查看 SPFILE 中的参数配置。
常见字段包括:
text
sid 参数作用对象
name 参数名
value 参数值
ordinal 多值参数的序号
isspecified 是否在 SPFILE 中显式指定
其中,sid 字段比较关键。
如果参数在 SPFILE 中是这样配置的:
text
*.open_cursors=500
那么 v$spparameter 中的 sid 通常为:
text
*
表示该参数对所有实例生效。
如果参数是这样配置的:
text
orcl1.open_cursors=800
那么 sid 通常为:
text
orcl1
表示该参数只对实例 orcl1 生效。
在 RAC 环境中,实例级参数优先级高于全局参数。也就是说,如果同时存在:
text
*.open_cursors=500
orcl1.open_cursors=800
那么对于实例 orcl1 来说,实际 SPFILE 中更合理的生效值应该是:
text
800
这一点在脚本中需要特别处理。
3. gv$parameter
gv$parameter 用于查看当前内存中的参数值。
常见字段包括:
text
inst_id 实例 ID
name 参数名
value 当前内存中的参数值
这个视图反映的是数据库实例当前运行时的参数值。
三、脚本需要解决的问题
在编写这个脚本时,需要处理几个常见问题。
1. 全局参数和实例级参数同时存在
例如:
text
*.open_cursors=500
orcl1.open_cursors=800
对于实例 orcl1,不能简单地把 500 和 800 都拿出来比较,而是要优先使用实例级配置 800。
2. 多值参数聚合
有些 Oracle 参数是多值参数,例如一些路径转换、文件列表类参数。
例如:
text
db_file_name_convert
log_file_name_convert
这类参数可能包含多个值。在 v$spparameter 中,多值参数可能会以多行形式存在,因此脚本中使用了 LISTAGG 对多个值进行聚合。
3. 空格导致的误报
有些参数在内存视图中显示时,逗号后面会带空格,例如:
text
/u01/app/oracle/oradata/, +DATA/orcl/datafile/
而 SPFILE 中可能是:
text
/u01/app/oracle/oradata/,+DATA/orcl/datafile/
两者实际语义一致,但如果直接比较字符串,会认为参数不一致。因此脚本中使用:
sql
REGEXP_REPLACE(value, '[[:space:]]*,[[:space:]]*', ',')
将逗号前后的空格统一去掉。
4. NULL 值比较
如果一边是 NULL,另一边不是 NULL,普通比较:
sql
a <> b
可能无法正确识别差异。因此脚本中显式处理了 NULL:
sql
(p.spfile_value IS NULL AND m.value IS NOT NULL)
OR
(p.spfile_value IS NOT NULL AND m.value IS NULL)
四、完整巡检 SQL
以下脚本可用于比对 SPFILE 参数值与当前内存参数值之间的差异。
适用版本:
text
Oracle 11gR2
Oracle 11gR2 RAC
Oracle 12c
Oracle 18c
Oracle 19c
如果是 Oracle 11.1 或更早版本,可能不支持 LISTAGG,需要根据实际情况调整。
完整 SQL 如下:
sql
WITH inst AS (
SELECT inst_id, instance_name FROM gv$instance
),
spfile_raw AS (
SELECT i.inst_id, s.name, s.value, s.ordinal, 1 AS priority
FROM v$spparameter s CROSS JOIN inst i
WHERE s.sid = '*' AND s.isspecified = 'TRUE'
UNION ALL
SELECT i.inst_id, s.name, s.value, s.ordinal, 2 AS priority
FROM v$spparameter s JOIN inst i ON UPPER(s.sid) = UPPER(i.instance_name)
WHERE s.isspecified = 'TRUE'
),
spfile_effective AS (
SELECT inst_id, name, value, ordinal, priority,
MAX(priority) OVER (PARTITION BY inst_id, name) AS effective_priority
FROM spfile_raw
),
spfile_agg AS (
SELECT inst_id, name,
LISTAGG(value, ',') WITHIN GROUP (ORDER BY NVL(ordinal, 0), value) AS spfile_value
FROM spfile_effective
WHERE priority = effective_priority
GROUP BY inst_id, name
)
SELECT p.inst_id, i.instance_name, p.name,
p.spfile_value AS value_spfile,
m.value AS value_memory
FROM spfile_agg p
JOIN inst i ON i.inst_id = p.inst_id
JOIN gv$parameter m ON m.inst_id = p.inst_id AND m.name = p.name
WHERE (p.spfile_value IS NULL AND m.value IS NOT NULL)
OR (p.spfile_value IS NOT NULL AND m.value IS NULL)
OR UPPER(TRIM(REGEXP_REPLACE(p.spfile_value, '[[:space:]]*,[[:space:]]*', ','))) <>
UPPER(TRIM(REGEXP_REPLACE(m.value, '[[:space:]]*,[[:space:]]*', ',')))
ORDER BY p.name, p.inst_id;
五、脚本逐层解析
这个 SQL 使用了多个 CTE,也就是 WITH 子查询,便于分步处理逻辑。
1. 获取实例列表
sql
WITH inst AS (
SELECT inst_id, instance_name FROM gv$instance
)
这一段用于获取当前数据库所有实例。在单实例环境下,只会返回一行;在 RAC 环境下,会返回多个实例。
例如:
text
INST_ID INSTANCE_NAME
---------- ---------------
1 orcl1
2 orcl2
2. 展开 SPFILE 中的参数配置
sql
spfile_raw AS (
SELECT i.inst_id, s.name, s.value, s.ordinal, 1 AS priority
FROM v$spparameter s CROSS JOIN inst i
WHERE s.sid = '*' AND s.isspecified = 'TRUE'
UNION ALL
SELECT i.inst_id, s.name, s.value, s.ordinal, 2 AS priority
FROM v$spparameter s JOIN inst i ON UPPER(s.sid) = UPPER(i.instance_name)
WHERE s.isspecified = 'TRUE'
)
这一段处理 v$spparameter 中的参数,分为两部分。
第一部分:处理 sid = '*' 的全局参数
sql
SELECT i.inst_id, s.name, s.value, s.ordinal, 1 AS priority
FROM v$spparameter s CROSS JOIN inst i
WHERE s.sid = '*' AND s.isspecified = 'TRUE'
如果参数配置为 *.open_cursors=500,那么这个参数对所有实例生效。因此这里通过 CROSS JOIN inst,将该参数扩展到所有实例。
例如:
text
*.open_cursors=500
会被转换成:
text
实例 1: open_cursors = 500
实例 2: open_cursors = 500
这里将全局参数的优先级定义为 1 AS priority。
第二部分:处理实例级参数
sql
SELECT i.inst_id, s.name, s.value, s.ordinal, 2 AS priority
FROM v$spparameter s JOIN inst i ON UPPER(s.sid) = UPPER(i.instance_name)
WHERE s.isspecified = 'TRUE'
如果参数配置为 orcl1.open_cursors=800,那么该参数只对实例 orcl1 生效。这里通过 UPPER(s.sid) = UPPER(i.instance_name) 将 SPFILE 中的 sid 与实例名进行匹配。实例级参数的优先级定义为 2 AS priority,高于全局参数的优先级 1。
3. 计算每个实例下参数的有效优先级
sql
spfile_effective AS (
SELECT inst_id, name, value, ordinal, priority,
MAX(priority) OVER (PARTITION BY inst_id, name) AS effective_priority
FROM spfile_raw
)
这一段的作用是:找出某个实例下,某个参数应该使用全局配置,还是实例级配置。
例如,某个实例同时存在:
text
*.open_cursors=500
orcl1.open_cursors=800
对于实例 orcl1:
text
全局参数 priority = 1
实例级参数 priority = 2
因此 MAX(priority) OVER (PARTITION BY inst_id, name) 会返回 2。后续只保留 priority = effective_priority 的记录,也就是只保留实例级配置。
4. 聚合多值参数
sql
spfile_agg AS (
SELECT inst_id, name,
LISTAGG(value, ',') WITHIN GROUP (ORDER BY NVL(ordinal, 0), value) AS spfile_value
FROM spfile_effective
WHERE priority = effective_priority
GROUP BY inst_id, name
)
这一段用于处理多值参数。有些参数在 v$spparameter 中可能有多行,例如某些文件路径转换类参数。这里使用 LISTAGG(value, ',') 将同一实例、同一参数的多个值合并成一行。排序规则为 ORDER BY NVL(ordinal, 0), value,优先按照 ordinal 排序,再按照参数值排序。
5. 与内存参数进行比较
最终查询:
sql
SELECT p.inst_id, i.instance_name, p.name,
p.spfile_value AS value_spfile,
m.value AS value_memory
FROM spfile_agg p
JOIN inst i ON i.inst_id = p.inst_id
JOIN gv$parameter m ON m.inst_id = p.inst_id AND m.name = p.name
WHERE (p.spfile_value IS NULL AND m.value IS NOT NULL)
OR (p.spfile_value IS NOT NULL AND m.value IS NULL)
OR UPPER(TRIM(REGEXP_REPLACE(p.spfile_value, '[[:space:]]*,[[:space:]]*', ','))) <>
UPPER(TRIM(REGEXP_REPLACE(m.value, '[[:space:]]*,[[:space:]]*', ',')))
ORDER BY p.name, p.inst_id;
这里将处理后的 SPFILE 参数值 p.spfile_value 与当前内存中的参数值 m.value 进行比较。比较前做了标准化处理:
sql
REGEXP_REPLACE(value, '[[:space:]]*,[[:space:]]*', ',')
该表达式会把逗号前后的空格去掉。例如:
text
/a/, +DATA/b/
会被标准化为:
text
/a/,+DATA/b/
这样可以避免因为逗号后空格导致的误报。
六、结果说明
如果查询结果为空,通常表示:当前 SPFILE 中显式指定的参数值与内存中的参数值没有明显差异。
如果有查询结果,则说明这些参数可能存在以下情况:
- 参数已经通过
SCOPE=SPFILE修改,但数据库尚未重启。 - 内存中参数被动态修改,但 SPFILE 未同步。
- 参数值存在格式差异,需要人工确认。
- RAC 环境中不同实例存在参数差异。
示例结果可能类似:
text
INST_ID INSTANCE_NAME NAME VALUE_SPFILE VALUE_MEMORY
-------- -------------- ------------------- ------------------ ------------------
1 orcl1 open_cursors 1000 500
1 orcl1 sga_target 17179869184 8589934592
这表示:
text
SPFILE 中 open_cursors = 1000
当前内存中 open_cursors = 500
通常意味着该参数修改后尚未重启生效,或者内存值被其他方式修改。
七、SQL*Plus 执行建议
如果你使用 SQL*Plus 执行,建议先设置一些显示参数,避免输出被截断。
例如:
sql
SET LINESIZE 32000
SET PAGESIZE 1000
COL instance_name FORMAT A20
COL name FORMAT A40
COL value_spfile FORMAT A80
COL value_memory FORMAT A80
然后再执行巡检 SQL。
如果在 SQL*Plus 中粘贴 SQL 时遇到类似报错:
text
ORA-00928: missing SELECT keyword
大概率不是 SQL 本身的问题,而是粘贴过程中出现了换行、注释、字符集或终端兼容性问题。
建议:
- 尽量使用无注释版本。
- 将 SQL 保存为
.sql文件,然后使用@文件名执行。 - 如果脚本中有空行,可以先执行:
sql
SET SQLBLANKLINES ON
然后再粘贴执行。
八、注意事项
1. 数据库必须使用 SPFILE
如果数据库使用的是 PFILE,而不是 SPFILE,那么 v$spparameter 中可能没有有效数据,脚本可能查询不到结果。
可以通过以下 SQL 确认当前是否使用 SPFILE:
sql
SELECT name, value
FROM v$parameter
WHERE name = 'spfile';
如果 value 为空,说明当前可能没有使用 SPFILE。
2. 执行用户需要具备字典视图查询权限
执行该脚本的用户需要能够访问动态性能视图和数据字典视图。
可以授予:
sql
GRANT SELECT ANY DICTIONARY TO username;
或者:
sql
GRANT SELECT_CATALOG_ROLE TO username;
也可以按需单独授权相关 V$、GV$ 视图。
3. LISTAGG 版本要求
脚本中使用了 LISTAGG,该函数通常在 Oracle 11gR2 及以上版本中可用。如果你的数据库版本较低,例如 Oracle 11.1 或 Oracle 10g,可能无法直接使用该脚本。
对于低版本环境,可以考虑:
- 使用
XMLAGG改写。 - 仅处理单值参数。
- 使用
ROW_NUMBER()取优先级最高的一条记录。
4. 19c 多租户环境需要注意
在 Oracle 12c、18c、19c 多租户环境中,参数体系会更复杂。如果是检查实例级或根容器参数,建议在 CDB$ROOT 中执行。如果是 PDB 级别参数,不能简单套用该脚本,需要结合 PDB 参数相关视图或内部表单独处理。
5. 某些参数可能存在语义等价但字符串不同
例如某些内存参数:
text
SPFILE: 1G
MEMORY: 1073741824
从字符串角度看不同,但语义上可能等价。对于这类情况,脚本可能会识别为差异。如果需要进一步确认,可以结合 DISPLAY_VALUE 或者人工判断参数单位。
九、可选增强
如果你希望进一步忽略所有空格,可以将比较条件改为:
sql
OR UPPER(REGEXP_REPLACE(p.spfile_value, '[[:space:]]', '')) <>
UPPER(REGEXP_REPLACE(m.value, '[[:space:]]', ''))
这种方式会删除参数值中的所有空格。不过需要注意:如果参数值本身包含有意义的空格,例如某些路径中包含空格,那么这种方式可能会误判。因此,一般情况下更推荐使用本文脚本中的方式:
sql
REGEXP_REPLACE(value, '[[:space:]]*,[[:space:]]*', ',')
它主要处理逗号前后的空格,对大多数列表型参数更友好。
十、总结
本文提供的 SQL 可以用于 Oracle 数据库参数巡检,主要作用是比较 SPFILE 中显式指定的参数值与当前内存中的参数值之间的差异。
脚本重点处理了以下几个问题:
sid='*'全局参数与实例级参数同时存在时的优先级问题。- RAC 环境下多实例参数比对。
- 多值参数的聚合处理。
- 逗号前后空格导致的误报。
NULL值差异识别。- 兼容 Oracle 11gR2 到 Oracle 19c 的常见运维场景。
在数据库变更、参数巡检、RAC 一致性检查、重启前检查等场景中,这个脚本都可以作为日常运维工具使用。
十一、附:可直接执行版本
sql
WITH inst AS (
SELECT inst_id, instance_name FROM gv$instance
),
spfile_raw AS (
SELECT i.inst_id, s.name, s.value, s.ordinal, 1 AS priority
FROM v$spparameter s CROSS JOIN inst i
WHERE s.sid = '*' AND s.isspecified = 'TRUE'
UNION ALL
SELECT i.inst_id, s.name, s.value, s.ordinal, 2 AS priority
FROM v$spparameter s JOIN inst i ON UPPER(s.sid) = UPPER(i.instance_name)
WHERE s.isspecified = 'TRUE'
),
spfile_effective AS (
SELECT inst_id, name, value, ordinal, priority,
MAX(priority) OVER (PARTITION BY inst_id, name) AS effective_priority
FROM spfile_raw
),
spfile_agg AS (
SELECT inst_id, name,
LISTAGG(value, ',') WITHIN GROUP (ORDER BY NVL(ordinal, 0), value) AS spfile_value
FROM spfile_effective
WHERE priority = effective_priority
GROUP BY inst_id, name
)
SELECT p.inst_id, i.instance_name, p.name,
p.spfile_value AS value_spfile,
m.value AS value_memory
FROM spfile_agg p
JOIN inst i ON i.inst_id = p.inst_id
JOIN gv$parameter m ON m.inst_id = p.inst_id AND m.name = p.name
WHERE (p.spfile_value IS NULL AND m.value IS NOT NULL)
OR (p.spfile_value IS NOT NULL AND m.value IS NULL)
OR UPPER(TRIM(REGEXP_REPLACE(p.spfile_value, '[[:space:]]*,[[:space:]]*', ','))) <>
UPPER(TRIM(REGEXP_REPLACE(m.value, '[[:space:]]*,[[:space:]]*', ',')))
ORDER BY p.name, p.inst_id;