Oracle概要文件实战:资源限制、密码策略与ORA-28000排查 Oracle 里的概要文件profile这个对象平时存在感很低出问题时存在感极高。我印象最深的一次是某套业务系统早上八点整批量报错日志里刷满了 ORA-28000 账户已被锁定值班的 A 同学第一反应是数据库密码被人改了查了半小时才发现是三个月前有人把 DEFAULT 概要文件的口令限制参数一起改掉了导致全实例所有用户的失败登录次数阈值从 10 次变成了 3 次几个跑批程序的旧连接重试三次就被锁死。这件事之后我养成了一个习惯只要动概要文件先想清楚影响面再动手。概要文件本质上就是一份用户级的资源配额 密码策略档案它管两件事——一个用户能消耗多少数据库资源会话数、CPU 时间、连接时长、逻辑读、私有 SGA 等以及这个用户的密码怎么管有效期、复用、失败锁定、复杂度校验。它解决的是多用户共用一套数据库时一个人把资源吃光、一个弱密码拖垮整个安全基线的问题。这篇内容适合已经能建用户、能写基本 SQL 的 DBA 和开发也适合刚接手一套现成库、看到一堆 ORA-28xxx 报错不知道从哪下手的朋友。我按自己踩坑的顺序把参数含义、生效条件、实操脚本和排查套路一次性讲透。1. 概要文件到底是什么从一次账号被锁说起1.1 概要文件在用户体系里的位置理解概要文件最好把它和用户、权限放在一张图里看。Oracle 里一个用户user/schema身上挂着三样东西身份密码、认证方式、权限系统权限、对象权限、角色、以及配额概要文件。前两样决定了你能干什么概要文件决定的是你干这件事的时候能用多少、密码要符合什么规矩。它不像表空间配额那样直接卡住 INSERT而是在会话建立和语句执行的过程中由后台持续计量超了就直接把会话杀掉或者拒绝后续操作。这个设计思路很像公司给员工发的门禁卡权限决定你能进哪些房间配额决定你一天能进几次、每次待多久、刷卡失败几次卡就被冻结。概要文件的绑定关系非常干脆——一个用户只能绑一个概要文件多对一不能叠加。想给不同业务不同策略就得建多个概要文件再分别 ALTER USER 指定。这点和角色完全不同角色可以叠加、可以嵌套概要文件不行。所以实际项目里常见的做法是按业务类型 连接方式分类建概要文件比如 OLTP 在线交易一套、批处理一套、只读报表一套、外部接口一套而不是一个用户建一个那样后期维护会疯掉。我见过一个库里有七十多个概要文件名字全是 PROFILE_01 到 PROFILE_70没人说得清哪个是干什么的参数也各不相同最后只能重建一套标准化的再逐个迁移。需要特别强调的是 DEFAULT 这个概要文件的地位。它是实例创建时自带的、不可删除的默认档所有新建用户如果没有显式指定 PROFILE 子句自动继承 DEFAULT。这意味着你对 DEFAULT 的任何一次修改都会瞬间作用到所有还在用默认档的用户身上。很多事故的根源就在这里本意是想管住某个测试账号结果手一抖改的是 DEFAULT。1.2 DEFAULT 概要文件的特殊地位与继承逻辑DEFAULT 概要文件的特殊性体现在三个方面。第一它不能被 DROPDROP PROFILE DEFAULT会直接报 ORA-00933 或权限类错误因为系统必须有兜底档。第二删除任何自定义概要文件时如果没有加 CASCADE只要还有用户绑在上面就会报 ORA-02382profile 存在依赖用户加了 CASCADE那些用户的概要文件会被自动重置回 DEFAULT参数随之变成默认值这个静默回退是最容易被忽略的坑因为删除当时不报错业务下次登录才发现策略变了。第三DEFAULT 的口令参数一旦设置新密码策略对所有默认用户立即生效但已经连接着的会话不会被踢掉它们会在下一次认证或者下一次资源计量刷新时才感知到变化。这里有个非常容易混淆的点概要文件本身没有继承机制。不存在我的自定义档没写的参数自动继承 DEFAULT这种设计。实际上CREATE PROFILE ... LIMIT里没写的参数会取建库时定义的默认值大多数是 UNLIMITED口令类参数在较新版本里有些会给默认值比如密码验证函数会挂上系统自带的校验函数而不是实时读取 DEFAULT 的当前值。也就是说如果今天你把 DEFAULT 的 FAILED_LOGIN_ATTEMPTS 改成 5你那个三个月前建的自定义概要文件里的这个参数不会跟着变成 5它还是当初创建时的取值。这个行为我建议所有人在心里默念三遍因为它直接决定了我改 DEFAULT 为什么这个用户没反应这类问题的答案。提示动 DEFAULT 概要文件之前先跑一句SELECT username FROM dba_users WHERE profileDEFAULT;看清楚这一改会波及多少个账号里面有系统账号、有第三方监控账号、有早就没人管的僵尸账号。这一步花三十秒能省掉后面三小时的排障。从影响范围来讲概要文件的改动可以分成三个圈层。最内层是单个用户ALTER USER 改绑只影响他自己中间层是一个自定义概要文件上的所有用户改参数时影响这一批最外层是 DEFAULT改它等于改全库。所以我的操作顺序永远是先建新档、验证参数、灰度绑一个非关键账号跑几天、确认没问题再批量迁移、最后才考虑要不要动 DEFAULT。这个顺序看着啰嗦但在生产环境上它的性价比高得离谱。2. 资源限制参数把CPU、会话、连接时间关进笼子2.1 会话级与调用级资源参数逐个拆解资源限制参数分两个粒度SESSION 级和 CALL 级。SESSION 级是从登录到断开的整个生命周期内累计CALL 级是单次解析执行一次 parseexecutefetch内累计。理解这个区别很重要因为 CALL 级限制是防止单条烂 SQL 长时间占着 CPUSESSION 级限制是防止某个应用整天挂着不放。两类参数的单位和含义差别很大我用一张表来对齐参数粒度单位典型取值思路SESSIONS_PER_USER会话个数按应用连接池上限的 1.2 倍给CPU_PER_SESSION会话1/100 秒累计 CPU按业务单次跑批峰值估CPU_PER_CALL调用1/100 秒防单条 SQL 打满通常给 60~300 秒CONNECT_TIME会话分钟连接总存活时长批处理给足IDLE_TIME会话分钟空闲多久被踢注意连接池心跳LOGICAL_READS_PER_SESSION会话数据块数累计逻辑读防全表扫雪崩LOGICAL_READS_PER_CALL调用数据块数单条语句读块上限PRIVATE_SGA会话K / M仅共享服务器模式下有意义COMPOSITE_LIMIT会话service units多资源加权综合上限CPU 相关的两个参数单位是百分之一秒这个细节特别容易搞错。你看到CPU_PER_CALL 100000别以为是十万秒实际是 1000 秒也就是 16 分多钟。我见过有人按秒来填填了个 300结果等于 3 秒业务上一条稍微复杂点的查询直接报 ORA-02392 快照太旧类似的资源超限错误排查了半天才发现单位错了。所以填这类参数之前先在心里做一次除法把 1/100 秒换算成你熟悉的秒数再判断合不合理。IDLE_TIME 和 CONNECT_TIME 都是分钟但它们的杀伤力完全不一样。IDLE_TIME 超了会话是被动的、非正常断开客户端下次用这条连接会收到 ORA-02396 或者连接已断开的错误应用如果没有重连机制就会直接崩CONNECT_TIME 超了也是类似效果。所以我给生产环境的建议是IDLE_TIME 不要设置得比应用连接池的 keepalive 间隔更短比如连接池每 5 分钟做一次心跳检测你 IDLE_TIME 设 3 分钟那就是自己给自己找事。批处理类应用的 CONNECT_TIME 必须给足因为一个大跑批挂四五个小时很正常你给 120 分钟跑到一半连接被杀前面的活全白干。SESSIONS_PER_USER 看着最简单其实也有讲究。它不是同时活跃会话数而是这个用户当前存在的会话总数包括那些已经空闲但还没被回收的连接。连接池动不动就预热几十条连接你按实际并发去估就会踩坑。我的经验公式是连接池最大连接数 × 1.3再往上留点余量给运维手工登录排查用。如果应用有多个节点每个节点都开池子那就要乘节点数。这个数字算错了的后果是高峰期随机报 ORA-00018 maximum number of sessions exceeded 或者更隐蔽的偶发登录失败。2.2 COMPOSITE_LIMIT 的加权算法与参数取值推演COMPOSITE_LIMIT 是整个资源参数里最玄的一个它把 CPU_PER_SESSION、CONNECT_TIME、LOGICAL_READS_PER_SESSION、PRIVATE_SGA 四项折算成统一的 service units 再求和只要总和超了就直接拒绝。理论上这是个很优雅的设计——一条总闸门管住四条支流。但实际用起来我建议大多数场景直接设 UNLIMITED原因很简单Oracle 并没有把权重换算公式完整公开你没法像算预算那样精确推导我给 50000 units 到底相当于多少 CPU 秒加多少逻辑读。我做过一些粗糙的实测在测试库上用单个会话反复压测观察V$SESSMETRIC和V$SESSTAT的变化来反推比例关系。结论只能说是数量级参考不同版本、不同负载形态下偏差不小。所以我的实操建议是先不用 COMPOSITE_LIMIT而是把四项单独设好。这四项各自都会独立生效等于四道闸门已经拦住了大部分风险。等你跑了几个月从 AWR 或者会话历史里摸清楚正常业务的资源画像再决定要不要上综合限额。真要上也别拍脑袋按正常峰值 × 2给留足缓冲。至于 PRIVATE_SGA它只在共享服务器shared server模式下才起作用因为专用服务器模式下每个会话的私有内存本来就不受这个参数管。现在绝大多数系统都跑专用服务器所以这个参数设了基本等于没设。不过我还是建议显式写成 UNLIMITED 而不是留空因为留空时取的是创建时的默认值将来万一有人切换到共享服务器模式这些历史遗留的奇怪数值会突然开始生效那种问题排查起来非常难受。2.3 资源限制生效的前置条件RESOURCE_LIMIT 与资源计划这是新手最容易翻车的地方参数建好了、用户也绑了、跑测试却一点效果都没有以为是自己写错了。其实概要文件的资源限制注意是资源限制口令限制不受此影响默认是不生效的因为RESOURCE_LIMIT这个初始化参数默认是 FALSE。你必须显式打开它-- 查看当前开关状态 SHOW PARAMETER resource_limit; -- 打开动态生效不需要重启 ALTER SYSTEM SET resource_limit TRUE SCOPE BOTH;为什么默认关着因为开启后每次语句执行都要做一些额外的资源计量会有轻微性能开销而且很多系统本来就靠应用层限流不需要数据库再管一层。但对于有多租户、有外部接口账号、有临时排查账号的环境我强烈建议打开。开销实测下来在正常 OLTP 负载下几乎测不出来换来的是资源不被单个会话拖垮的确定性。第二个前置条件是资源管理器Resource Manager。如果你启用了某个 resource plan那么 CPU 相关的限制CPU_PER_SESSION、CPU_PER_CALL、COMPOSITE_LIMIT 里的 CPU 部分就交给计划去管了概要文件里的这些参数会被忽略但 SESSIONS_PER_USER、IDLE_TIME、CONNECT_TIME 这几项仍然由概要文件控制。这个分工很多人不清楚导致我明明在 profile 里限制了 CPU 怎么没起作用的疑问。查一下当前有没有启用计划SELECT plan, is_top_plan FROM v$rsrc_plan;如果有输出且 IS_TOP_PLAN 为 TRUE说明资源计划正在生效。这时候你调整 profile 里的 CPU 参数是白费劲应该去改对应的资源计划。3. 口令限制参数密码策略的落地方式3.1 生命周期、复用、锁定与宽限期四件套口令限制参数是概要文件里真正天天被感知的部分因为几乎所有 ORA-28xxx 报错都跟它们有关。核心就六个参数我把它们的相互作用关系理一遍这比单独背定义有用得多。PASSWORD_LIFE_TIME 是密码有效期天从最后一次改密开始算。超了之后进入 PASSWORD_GRACE_TIME 宽限期这段时间内还能登录但每次登录都会收到 ORA-28002 警告——注意是警告不是错误很多应用把它当错误处理就去改代码了其实是应用日志解析太粗暴。宽限期过了就变成 ORA-28001密码彻底过期必须改密才能连。PASSWORD_REUSE_TIME 和 PASSWORD_REUSE_MAX 是一对连体参数管密码复用。前者是隔多少天才能重用旧密码后者是要改多少次之后才能重用旧密码。这里有个必须记住的规则这两个参数要么都设成具体数值要么都设成 UNLIMITED如果一个设了值另一个是 UNLIMITED结果不是放宽而是密码永远无法重用因为两个条件需要同时满足UNLIMITED 那个条件在逻辑上永远成立不了。这个行为我专门验证过确实如此网上一堆人说设一个就够是错的。FAILED_LOGIN_ATTEMPTS 和 PASSWORD_LOCK_TIME 是锁定组合。连续输错 N 次账号被锁锁 PASSWORD_LOCK_TIME 天。这里有两个细节一是连续的判定——只要中间成功登录一次计数就清零二是 PASSWORD_LOCK_TIME 如果设成 UNLIMITED那锁上之后只能 DBA 手工解锁对于无人值守的系统来说这是灾难因为一旦被撞库锁死业务就彻底断了。我的建议是给具体天数比如 1 天或者 1/24 天一小时让系统能自愈。INACTIVE_ACCOUNT_TIME 是较新版本引入的含义是多少天没登录就自动锁定账号。这个参数对付僵尸账号特别有效很多安全审计要求的长期未使用账号必须禁用可以直接用它落地。但要注意它和 FAILED_LOGIN_ATTEMPTS 的锁定是两种不同的锁定状态在 DBA_USERS 的 ACCOUNT_STATUS 里显示的文字不一样解锁方式都是 ALTER USER ... ACCOUNT UNLOCK。参数单位UNLIMITED 的含义常见坑PASSWORD_LIFE_TIME天永不过期设太短导致频繁改密应用存了硬编码密码就崩PASSWORD_GRACE_TIME天宽限期无限等于密码事实上永不过期PASSWORD_REUSE_TIME天见上文连体规则与 REUSE_MAX 不匹配导致无法改密PASSWORD_REUSE_MAX次见上文连体规则同上FAILED_LOGIN_ATTEMPTS次永不锁定给撞库留了口子PASSWORD_LOCK_TIME天锁定不解需要人工介入无人值守系统慎用INACTIVE_ACCOUNT_TIME天不检查较新版本才有3.2 密码复杂度校验函数 PASSWORD_VERIFY_FUNCTION 的写法与注册前面那些参数管的是密码多久换、能不能复用管不了密码是不是 123456。要卡复杂度必须挂一个校验函数。这个函数的签名是固定的三参数用户名、新密码、旧密码返回 BOOLEAN。函数体里通过抛出异常最好是 RAISE_APPLICATION_ERROR 配合自定义错误号来拒绝不合规的密码正常通过就 RETURN TRUE。下面这个版本是我在多个项目里用过的精简版长度、字符类型、常见弱词、与用户名相同这几项都卡了CREATE OR REPLACE FUNCTION verify_password_fn( p_userid IN VARCHAR2, p_password IN VARCHAR2, p_old_password IN VARCHAR2 ) RETURN BOOLEAN IS v_len NUMBER : NVL(LENGTH(p_password), 0); v_upper NUMBER : 0; v_lower NUMBER : 0; v_digit NUMBER : 0; v_spec NUMBER : 0; v_ch VARCHAR2(1); BEGIN IF v_len 10 THEN RAISE_APPLICATION_ERROR(-20001, 密码长度不得少于10位); END IF; IF UPPER(p_password) UPPER(p_userid) THEN RAISE_APPLICATION_ERROR(-20002, 密码不得与用户名相同); END IF; IF p_old_password IS NOT NULL AND UPPER(p_password) UPPER(p_old_password) THEN RAISE_APPLICATION_ERROR(-20003, 新密码不得与旧密码相同); END IF; IF INSTR(LOWER(p_password), password) 0 OR INSTR(LOWER(p_password), admin) 0 OR INSTR(LOWER(p_password), 123456) 0 THEN RAISE_APPLICATION_ERROR(-20004, 密码包含常见弱口令片段); END IF; FOR i IN 1 .. v_len LOOP v_ch : SUBSTR(p_password, i, 1); IF v_ch BETWEEN A AND Z THEN v_upper : v_upper 1; ELSIF v_ch BETWEEN a AND z THEN v_lower : v_lower 1; ELSIF v_ch BETWEEN 0 AND 9 THEN v_digit : v_digit 1; ELSE v_spec : v_spec 1; END IF; END LOOP; IF v_upper 0 OR v_lower 0 OR v_digit 0 OR v_spec 2 THEN RAISE_APPLICATION_ERROR(-20005, 密码须同时包含大写字母、小写字母、数字及至少2个特殊字符); END IF; RETURN TRUE; END verify_password_fn; /几个实操要点。第一函数必须在 SYS 下创建或者创建后授权到位因为数据库在做密码校验时是以内部身份调用的执行权限不够会直接报 ORA-28003 或者更含糊的密码验证失败。稳妥做法是建完立刻GRANT EXECUTE ON verify_password_fn TO PUBLIC;。第二函数里不要做任何 DML也不要查业务表密码校验发生在认证阶段这时候查表极可能因为递归 SQL 或者权限问题直接挂掉我见过有人在校验函数里 SELECT 一张黑名单表结果那张表所在表空间满了全库用户都改不了密码。第三改完函数记得重新编译并且用一个测试账号实际改一次密码验证别只看编译通过就完事。还有一个坑中之坑如果你在概要文件里设置了任何口令限制参数但 PASSWORD_VERIFY_FUNCTION 留空较新版本的 Oracle 会自动挂上系统自带的默认校验函数。这个默认函数的要求未必符合你的预期可能比你想的严格也可能更宽松。所以我在建档时永远显式写清楚这一项要自定义就写函数名不要就写 NULL绝不留空。4. 实操全过程从建profile到验证生效4.1 创建、修改、分配与查询的完整脚本假设现在要给一套在线交易应用建一套策略会话数限制住防止连接池泄漏、CPU 单语句不能跑太久、空闲连接一小时内回收、密码 90 天换一次、错 5 次锁 1 小时。完整脚本如下CREATE PROFILE app_oltp_profile LIMIT SESSIONS_PER_USER 20 CPU_PER_SESSION UNLIMITED CPU_PER_CALL 30000 CONNECT_TIME 720 IDLE_TIME 60 LOGICAL_READS_PER_SESSION UNLIMITED LOGICAL_READS_PER_CALL 200000 PRIVATE_SGA UNLIMITED COMPOSITE_LIMIT UNLIMITED FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LIFE_TIME 90 PASSWORD_REUSE_TIME 180 PASSWORD_REUSE_MAX 5 PASSWORD_LOCK_TIME 1/24 PASSWORD_GRACE_TIME 7 INACTIVE_ACCOUNT_TIME 90 PASSWORD_VERIFY_FUNCTION verify_password_fn;几个取值我解释一下为什么这么定。CPU_PER_CALL 给 30000即 300 秒是因为在线交易单条语句正常在毫秒级给到 5 分钟已经是极大的冗余超过这个时间的语句基本可以判定是 Bug 或者缺索引杀掉不冤。LOGICAL_READS_PER_CALL 给 200000 块按 8K 块大小算大约 1.5GB 的逻辑读一个正常的点查或小范围扫描远远到不了而一个全表扫会瞬间超限正好起到拦住坏 SQL的作用。CONNECT_TIME 给 720 分钟是给批量对账留的余量IDLE_TIME 给 60 分钟则是配合连接池的保活周期一般连接池 30 分钟探活一次60 分钟就能保证活跃连接不被误杀。分配很简单ALTER USER app_user PROFILE app_oltp_profile;这里有个必须知道的限制ALTER USER 改 profile 只影响之后的会话当前已经连着的会话仍然用旧的概要文件直到它重新连接。所以改绑之后你要么等业务自然重连要么在维护窗口做一次连接池重启。指望改完立刻生效去验证大概率会被误导。查询概要文件内容用 DBA_PROFILES这个视图按行展开一行一个参数比看建表语句直观SELECT resource_name, limit FROM dba_profiles WHERE profile APP_OLTP_PROFILE ORDER BY resource_type, resource_name;想快速对比自定义档和 DEFAULT 的差异用自连接最省事SELECT a.resource_name, a.limit AS custom_limit, d.limit AS default_limit FROM dba_profiles a JOIN dba_profiles d ON a.resource_name d.resource_name AND d.profile DEFAULT WHERE a.profile APP_OLTP_PROFILE AND a.limit d.limit;这条查询我几乎每次接手新库都会跑一遍能一眼看出这个档到底被改过什么比逐行看省时间。想批量把一批用户迁到新档也可以让数据库帮你生成语句SELECT ALTER USER || username || PROFILE app_oltp_profile; FROM dba_users WHERE profile DEFAULT AND username NOT IN (SYS,SYSTEM,DBSNMP,OUTLN,APPQOSSYS) ORDER BY username;生成出来先看一遍确认没有系统账号混进去再复制执行。我习惯把生成结果先存成一个 .sql 文件加上注释和执行时间戳将来出问题能追溯。4.2 验证与回滚怎么确认真的生效了参数改完不验证等于没做。验证分三层。第一层查视图确认参数值确实写进去了第二层查用户绑定关系确认目标账号真的挂在新档上第三层做行为验证真正去触发一次限制。行为验证口令部分最简单拿一个测试账号故意输错密码 5 次然后看账号状态SELECT username, account_status, expiry_date FROM dba_users WHERE username TEST_USER;正常情况下 STATUS 会变成 LOCKED(TIMED)意思是被锁但有时间限制到点自动解开。如果显示 LOCKED 没有 TIMED 后缀说明 PASSWORD_LOCK_TIME 是 UNLIMITED得手工解锁。验证完记得ALTER USER test_user ACCOUNT UNLOCK;别把测试账号留着锁住忘了。验证资源限制需要点技巧因为很多限制在测试环境不容易触发。验证 SESSIONS_PER_USER 最直接用同一个账号开若干个 SQL*Plus 窗口一路连连到第 N1 个的时候会报 ORA-02391 exceeded simultaneous SESSIONS_PER_USER limit。验证 IDLE_TIME 就开一个会话挂在那儿什么都不做等到超过阈值之后随便发一条 SQL会收到 ORA-02396 提示会话因为空闲时间过长已被终止。验证 CPU_PER_CALL可以跑一条笛卡尔积的烂 SQL比如对一张大表做无条件自连接跑够时间就能看到 ORA-02392。这些验证一定要在测试环境做而且做完把证据报错截图、时间点记下来将来验收或者审计都用得上。回滚方案要提前准备好。最轻量的是把用户绑回原档ALTER USER app_user PROFILE DEFAULT;。如果整个自定义档都要撤先确认没用户绑着再DROP PROFILE app_oltp_profile;。想连用户迁移一起回滚就加 CASCADE但要清楚 CASCADE 意味着那些用户全被打回 DEFAULT参数会变成默认值——如果默认值里有你没料到的限制回滚本身会引发新问题。所以我个人习惯是删档之前先把绑定的用户列表导出来存一份实在不行还能照着恢复。监控层面日常巡检可以挂两条查询。一条看有没有用户被锁SELECT username, account_status, lock_date FROM dba_users WHERE account_status LIKE %LOCKED% OR account_status LIKE %EXPIRED%;另一条看密码快过期的账号提前通知业务方改密比等他们报错强SELECT username, expiry_date, TRUNC(expiry_date - SYSDATE) AS days_left FROM dba_users WHERE expiry_date IS NOT NULL AND expiry_date - SYSDATE 15 ORDER BY expiry_date;4.3 profile 的导出、迁移与批量管理概要文件这东西跨环境迁移特别容易漏。表结构、索引、权限都能用数据泵带过去但概要文件绑在用户上数据泵导用户的时候其实会带上只是常常因为目标环境没有同名 profile 而静默回退到 DEFAULT然后你发现测试环境一切正常、生产环境参数完全不一样。稳妥做法是单独把 profile 的 DDL 抽出来用 DBMS_METADATASET LONG 200000 SET PAGESIZE 0 SELECT DBMS_METADATA.GET_DDL(PROFILE, APP_OLTP_PROFILE) FROM dual;拿到 DDL 之后注意两件事一是 DDL 里不含校验函数的创建语句函数得单独迁二是如果目标库已经有同名 profile直接执行会报错得改成先 DROP 再 CREATE或者手工拼成 ALTER PROFILE。我一般的做法是把导出的 DDL 和校验函数脚本放在同一个部署包里加一个前置脚本检查目标库是否已存在同名 profile存在就先备份现有定义再覆盖。批量管理可以再进一步把所有 profile 的定义导出成一张对照表方便做基线比对。思路很简单把 DBA_PROFILES 的结果按 profile 做行转列或者干脆导成 CSV 存进版本库。我们现在的做法是每次变更后都导一份快照放 Git 里两个版本一 diff 就知道谁改了什么。这个习惯帮我抓到过一次某个概要文件被临时改成 UNLIMITED 忘了改回来的问题如果没有基线这种改动几乎不可能被发现。5. 常见问题与排查实录5.1 五类高频报错的原因与处理概要文件相关的报错类型其实很集中我整理成速查表报错触发原因处理方式ORA-28000连续输错达阈值账号被锁确认为本人操作后ALTER USER xx ACCOUNT UNLOCKORA-28001密码超过有效期加宽限期改密改完立刻恢复ORA-28002密码进入宽限期只是提醒尽快改密应用侧别当错误处理ORA-28003密码未通过校验函数看函数里抛的自定义错误文案按提示改ORA-02391超出 SESSIONS_PER_USER查连接池配置或是否有泄漏连接ORA-02396空闲超时被终止检查 IDLE_TIME 与连接池保活间隔ORA-02392CPU 或逻辑读超限找对应 SQL 优化别急着放大参数ORA-01017用户名密码错误有时与锁定混淆先看账号状态再看密码ORA-28000 是最常见的也是最容易被误判的。要注意它有两种成因一种是失败次数达到 FAILED_LOGIN_ATTEMPTS另一种是撞上了 INACTIVE_ACCOUNT_TIME 的长期未登录锁定。两者在 DBA_USERS 里显示的 ACCOUNT_STATUS 文案不同前者是 LOCKED(TIMED) 或 LOCKED后者是 LOCKED(TIMED) 加一个不同的描述具体文案随版本略有差异。排查时先看 LOCK_DATE 和账号最近一次登录时间可以从审计或者 V$SESSION 历史里找能快速区分。如果是长期未登录导致的那说明这个账号本身可能就是废弃的解锁前先确认业务是否还需要它。ORA-28002 这个警告式报错值得单独说。它在登录成功的同时返回SQL*Plus 里能看到一句提示但如果应用用的是某些驱动在异常处理上可能会把它当致命错误抛出来导致明明能连却报登录失败。遇到这种密码没错但应用连不上的情况先看应用日志里有没有 28002如果有就是应用把警告当错误了要么改应用的错误过滤逻辑要么干脆把 PASSWORD_GRACE_TIME 调大留出更长的缓冲窗口。5.2 改了不生效按这个顺序查我明明改了参数怎么没效果这类问题按下面的顺序查基本五分钟内能定位第一步确认改的是不是生效的那个档。查用户实际绑的 profileSELECT username, profile FROM dba_users WHERE usernameXXX;。很多情况下用户绑的根本不是你以为的那个档尤其是接手别人的库。第二步确认改的是不是当前值。DBA_PROFILES 里看到的是档的定义但用户的密码过期时间是从上次改密日算的你改 PASSWORD_LIFE_TIME 不会重置已有的过期时间。想立刻生效得执行ALTER USER xxx IDENTIFIED BY 临时密码;或者ALTER USER xxx PASSWORD EXPIRE;强制走一轮。第三步资源类限制查 RESOURCE_LIMIT 开关和资源计划。前面说过没开开关的话资源参数完全不生效这是最高频的原因。第四步确认会话是不是老的。改绑 profile 和修改口令参数对已经建立的会话不一定立即生效尤其是资源累计值不会清零。等新会话建立后再验证。第五步多租户环境要确认容器。较新版本里概要文件有本地和公共之分你在 PDB 里建的是本地档只在当前 PDB 可见如果在根容器里建公共档命名和可见性规则又不一样。跨容器排查时务必带上CON_ID一起看或者直接切到对应容器里查。我遇到过在根容器改了半天发现业务连的是某个 PDB白忙一场。5.3 一些踩坑心得与生产环境建议第一条心得永远不要在生产上用 DEFAULT 做试验田。想试参数就新建一个档绑一个测试用户跑跑通再推广。DEFAULT 的改动影响面是全库而且回滚时你未必记得住原来的每个参数值。我现在的习惯是每次动 DEFAULT 之前先SELECT * FROM dba_profiles WHERE profileDEFAULT;导出一份存文件命名带上时间戳回滚有据可依。第二条心得口令策略的调整要和应用方对齐。PASSWORD_LIFE_TIME 从 UNLIMITED 改成 90 天这种变更在很多老系统上是致命的因为应用可能把数据库密码写死在配置文件里密码一过期整个服务就起不来。上线前一定要问清楚密码存在哪、谁负责改、改密流程要多久。如果应用根本没能力定期改密那就老老实实给一个很长的有效期同时用强校验函数把密码强度拉起来用强度换时间这比硬上一个 90 天然后每周救火要务实得多。第三条心得概要文件的变更要进变更管理。它不像建表那样看得见摸得着改了很容易被忘掉半年后出问题没人想得起来是谁改的。我们现在的做法是所有 profile 变更都走同一个脚本模板模板头部强制填变更单号、影响用户列表、回滚语句执行完把 DBA_PROFILES 的输出追加到变更记录里。这套流程一开始大家嫌烦出过两次事之后没人抱怨了。第四条心得校验函数要考虑可维护性。别把规则写死在 PL/SQL 里就完事业务方过半年说特殊字符要求从 2 个改成 3 个你得改函数、重编译、还得担心影响正在改密的用户。我的做法是把阈值抽成一张小配置表或者用常量集中定义在函数头部改的时候只动一个数字风险可控。当然函数里查表这件事前面说过要谨慎配置表要么放固定表空间要么在函数里做异常兜底读不到就用硬编码默认值保证认证路径永远不断。第五条心得定期做一次影子演练。挑一个非核心业务账号故意让它密码过期、故意输错密码锁掉然后走一遍完整的发现、通知、处理流程看看监控能不能报出来、值班的人知不知道怎么解。这个演练成本很低但能暴露出一堆流程问题比如监控只报了锁没报过期、比如解锁脚本权限没给到值班同学。我在两个团队推过这个做法每次都能查出点东西。最后分享一个我自己常用的小技巧想快速看清一个用户身上到底有哪些限制在起作用不用一个个视图翻直接拼一条查询把用户、profile、关键参数一次拉出来SELECT u.username, u.profile, u.account_status, p.resource_name, p.limit FROM dba_users u JOIN dba_profiles p ON p.profile u.profile WHERE u.username APP_USER AND p.resource_name IN ( SESSIONS_PER_USER,IDLE_TIME,CONNECT_TIME, FAILED_LOGIN_ATTEMPTS,PASSWORD_LIFE_TIME, PASSWORD_GRACE_TIME,PASSWORD_LOCK_TIME, PASSWORD_REUSE_TIME,PASSWORD_REUSE_MAX) ORDER BY p.resource_name;这条语句在我处理某个账号行为异常类问题时出场率极高一眼就能看出这个账号是被哪条策略卡住的比翻文档快得多。概要文件这套机制本身不复杂复杂的是它跟用户、资源计划、多租户容器、应用连接池之间的相互作用把这些关系理顺了那些 ORA-28xxx 和 ORA-023xx 就不再是玄学而是一条条能对得上的规则。