1. 故障现象:当登录风暴遇上Oracle Bug
那天早上9点49分,客户的生产库突然开始报警。应用系统响应变得极其缓慢,部分用户直接收到超时错误,更糟糕的是后续用户完全无法连接数据库。但奇怪的是,已经连接的用户操作完全正常——这就像酒店前台突然瘫痪,新客人无法入住,但已入住的客人不受影响。
查看RAC环境(共6个节点,运行着5套数据库),只有jsbas数据库出现了问题。这个数据库实例运行在节点3、4、5、6上,其他数据库都正常工作。通过检查当前等待事件,发现了惊人的情况:
SELECT event, count(*) FROM gv$session_wait WHERE wait_class != 'Idle' GROUP BY event;结果显示1904个会话都在等待library cache lock!这个数字意味着整个数据库的并发处理能力已经完全瘫痪。更诡异的是,当我们手动杀掉这些会话后,系统又立即自动新建了300多个会话,最终不得不直接关闭实例5来止血。
2. 深入诊断:ASH视图中的蛛丝马迹
通过分析ASH(Active Session History)历史数据,我发现了关键时间点:
SELECT sample_time, COUNT(*) active_sessions FROM dba_hist_active_sess_history WHERE sample_time BETWEEN TO_DATE('2022-07-16 09:00','YYYY-MM-DD HH24:MI') AND TO_DATE('2022-07-16 09:20','YYYY-MM-DD HH24:MI') GROUP BY sample_time ORDER BY sample_time;数据显示在9:14:22到9:15:22这一分钟内,活动会话数从785暴涨到1524,增加了739个!进一步查看具体等待事件:
SELECT a.session_id, a.sql_id, a.event, a.blocking_session, a.blocking_session_status FROM dba_hist_active_sess_history a WHERE a.sample_time = TO_DATE('2022-07-16 09:14:53','YYYY-MM-DD HH24:MI:SS');在09:14:53这个时间点,约70个活动会话中,大部分都被少数几个会话阻塞。被阻塞会话的等待事件是library cache lock(SQL_ID为空),而阻塞会话的等待事件却是Memory: Reg/Dereg(SQL_ID为9zg9qd9bm4spu)。
3. 阻塞链分析:谁在锁住整个系统?
检查阻塞会话执行的SQL语句,发现了罪魁祸首:
UPDATE user$ SET spare6 = DECODE(to_char(:2, 'YYYY-MM-DD'), '0000-00-00', to_date(NULL), :2) WHERE user# = :1这条语句的作用是当用户会话首次连接数据库时,在user$系统表中记录登录时间(对应DBA_USERS视图的LAST_LOGIN字段)。问题在于——这个update操作正在阻塞其他所有尝试登录的会话!
通过分析阻塞链,发现所有阻塞会话最终都指向6个特定会话。这些会话卡在Memory: Reg/Dereg等待事件,而Oracle官方文档指出:这种等待通常由登录风暴(高并发连接请求)引发。
4. 根本原因:Bug 33121934的运作机制
在MOS(My Oracle Support)上搜索相关症状,最终锁定Bug 33121934。这个Bug的影响范围非常广:
- 影响版本:12.1.0.2至23.1以下所有版本
- 触发条件:高并发登录时更新user$表
- 根本原因:Oracle在处理用户登录时间戳更新时,会错误地持有library cache锁
具体来说,当大量会话同时执行update user$语句时:
- 首先会申请Memory: Reg/Dereg内存操作锁
- 然后错误地以排他模式持有library cache锁
- 其他会话需要解析登录SQL时被阻塞
- 连锁反应导致整个系统连接能力瘫痪
5. 临时解决方案:止血三连招
面对这种紧急情况,可以采取以下措施:
方法一:精准击杀阻塞会话
-- 查找阻塞会话 SELECT sid, serial#, username, program FROM v$session WHERE sid IN ( SELECT blocking_session FROM v$session WHERE wait_class != 'Idle' ); -- 杀死会话 ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;方法二:限制新连接
-- 设置连接数限制 ALTER SYSTEM SET processes=300 SCOPE=MEMORY; ALTER SYSTEM SET sessions=335 SCOPE=MEMORY;方法三:服务降级
# 通过应用层限制登录 # 例如在WebLogic中设置连接池最大等待时间 set weblogic.management.configuration.JDBCConnectionPoolParams.MaxWaitingForConnection=30006. 长期解决方案:防患于未然
彻底解决这个问题需要多管齐下:
应用层优化:
- 实现连接池预热,避免冷启动时的连接风暴
- 采用指数退避算法重试失败连接
- 示例Java连接池配置:
// HikariCP配置示例 HikariConfig config = new HikariConfig(); config.setMaximumPoolSize(100); config.setConnectionTimeout(30000); config.setInitializationFailTimeout(60000);数据库升级:
- 升级到19.14/19.15或更高版本
- 打上补丁33121934(需Oracle支持)
架构优化:
- 对于关键系统,建议实现读写分离
- 将登录验证等操作路由到只读实例
- 使用Oracle Active Data Guard架构
7. 经验总结与监控建议
经过这次事故,我总结了几个关键监控点:
预警指标:
library cache lock等待超过总等待时间的5%- 每秒新建会话数超过50个
update user$语句执行时间超过1秒
监控脚本:
-- 实时监控登录风暴风险 SELECT TO_CHAR(sysdate,'HH24:MI:SS') sample_time, COUNT(*) current_logins, SUM(CASE WHEN status='ACTIVE' THEN 1 ELSE 0 END) active_logins, SUM(CASE WHEN event='library cache lock' THEN 1 ELSE 0 END) lck_waits FROM v$session WHERE program LIKE '%JDBC%' OR program LIKE '%weblogic%';自动化处理方案:
#!/bin/bash # 当检测到异常时自动限制连接 lck_count=$(sqlplus -s / as sysdba <<EOF set heading off select count(*) from v\$session where event='library cache lock'; EOF) if [ $lck_count -gt 50 ]; then sqlplus / as sysdba <<EOF ALTER SYSTEM SET processes=200 SCOPE=MEMORY; EXEC DBMS_LOCK.SLEEP(60); ALTER SYSTEM SET processes=500 SCOPE=MEMORY; EOF fi这次故障让我深刻认识到,看似简单的登录操作,在Oracle内部竟能引发如此严重的连锁反应。现在我们在所有重要系统上都部署了连接风暴监控,毕竟预防永远比救火来得轻松。