我先把这个项目的背景说清楚。我们刚给一套跑了八年的Oracle 11.2.0.4库完成了19c升级,用的是目前较新的19.25.0.0.241015版本。整个升级过程下来,最大的感受是:升级本身没那么吓人,真正让人反复折腾的,是升级路径怎么规划、补丁版本怎么确认,以及升级之后冒出来的监听起不来、ORA-28547、DG主备出现gap这类问题。这篇我不讲基础安装教程,只讲从老版本升到19c怎么落地,以及我这些年在群里、论坛里收到频率最高的几个顶级Q&A,希望能帮你绕开几个大坑。
1. 升级路径怎么选:从11g/12c到19c的路线图
1.1 为什么是19c:版本定位和生命周期
很多客户问我第一句话就是:现在19c还能用几年?为什么一定要升19c?这里面有个特别容易被忽略的事实:Oracle 19c的内部版本号就是12.2.0.3,它属于12c这个家族的最后一个大版本更新,也是Oracle官方定义的长期支持版本。跟它同时期的21c、23c都是“创新版本”,生命周期短,更适合尝鲜,而19c的扩展支持可以一路走到2027年前后,这意味着在可预见的未来,19c就是企业非云环境下性价比最高的稳定版本。
另一个关键点:从19c开始,非CDB架构虽然在升级时可以保留,但已经被官方明确标记为“不建议继续使用”,后面更新的版本对非CDB的支持越来越少。所以如果你现在还在跑11g时代的普通实例,升到19c正好是一个把非CDB改造成CDB/PDB结构的机会。虽然“改架构”听起来像额外工作量,但长远看,这一步越早做越省事。
1.2 不同老版本对应的升级路线
升级路径不是想当然的直接覆盖,Oracle对不同版本的源库支持力度完全不同。我整理了一张实战中最高频的路径表:
| 源库版本 | 是否支持直升19c | 说明 |
|---|---|---|
| 10.2.0.x 及更早 | 否 | 必须先升到11.2.0.4或12.2,再升19c |
| 11.2.0.3 | 否 | 需要先补丁到11.2.0.4 |
| 11.2.0.4 | 是 | 最常见的升级源库,路径最成熟 |
| 12.1.0.2 | 是 | 可直接升,但要注意compatible参数 |
| 12.2.0.1 | 是 | 同属12.2系列,升级最顺滑 |
| 18c | 是 | 与19c血缘最近,基本像打补丁 |
这里要特别提醒:如果你的库还在10g甚至更老,不要想着一步到位。道理很简单,跨越多个大版本的升级会导致数据字典里的内部格式变化太大,Oracle官方压根没有对极老版本做直升认证,硬升大概率会在字典升级阶段报一堆奇奇怪怪的ORA错误。稳妥的做法是先拿到11.2.0.4这个“中转站”,再走主流直升路径。
1.3 版本号19.25.0.0.241015到底表示什么
很多人看到“19.25.0.0.241015”这个版本号就懵了,我简单拆一下:
- 19代表数据库主版本,也就是19c;
- 25代表这是第25个季度的Release Update(RU补丁版本);
- 241015代表补丁发布于2024年10月15日。
Oracle 19c从19.3基础版本开始,每个季度都会发布一个RU补丁包,把安全更新、bug修复、性能优化打包在一起。所以你在19c基础版本上打补丁到19.25,实际效果就是“2024年10月份前所有的累积修复都包含进去了”。我的建议是:新环境直接部署最新RU补丁,老环境升完19c后,第一时间把补丁也升到当前季度附近,避免拿个2019年的裸版本上线,那样反而会踩很多已经修掉的bug。
2. 升级前准备与升级实操
2.1 升级前必须完成的环境检查
升级最忌讳一上来就改环境,先把地基打牢。我每次做升级前都会过一遍下面几项,任何一项不过关都不动数据库:
- 磁盘空间:新ORACLE_HOME需要额外空间,升级过程中字典表会膨胀,undo表空间也要预留充足。我曾经见过升级到一半日志把磁盘塞满的案例,直接导致实例崩溃,只能靠备份恢复。
- RMAN全备:必须在升级前做一次完整备份,并确认备份文件能被Restore。有条件的话把备份拷贝到异机,别跟数据库放同一块盘。
- 失效对象检查:升级前先跑一遍
select owner, object_type, count(*) from dba_objects where status = 'INVALID' group by owner, object_type;,如果失效对象特别多,升级过程会更长,最好提前记录baseline。 - 兼容性参数:确认当前数据库的compatible值,不要把已经设置得很高的compatible降下来,否则很多新特性会不可用。
- 应用侧兼容性:JDBC驱动、中间件版本、OGG版本都要和19c匹配。尤其注意老旧的ojdbc6或11g的OCI客户端,强烈建议升级到ojdbc8及以上。
2.2 手工升级核心步骤:catctl并行方式
19c开始,官方主推的命令行升级方式是catctl.pl并行脚本,相比老版本的catupgrd.sql串行升级,速度能快不少。下面是我实测过的可靠流程:
第一步:用preupgrade.jar做全面体检
新19c软件安装完成后,不要着急动库,先跑预检查:
cd $ORACLE_HOME/rdbms/admin $ORACLE_HOME/jdk/bin/java -jar preupgrade.jar TERMINAL这个工具会生成一个风险报告,并输出preupgrade_fixup.sql和preupgrade_postupgradefixup.sql两个脚本。前者要在旧库上执行,处理已知风险;后者升级后再跑。这一步千万别跳过,它基本就是Oracle官方给的“考前划重点”。
第二步:切换到新ORACLE_HOME并启动到upgrade模式
export ORACLE_HOME=/u01/app/oracle/product/19.3/dbhome_1 export PATH=$ORACLE_HOME/bin:$PATH export ORACLE_SID=ORCL sqlplus / as sysdba SQL> startup upgrade;startup upgrade模式允许系统在执行字典升级时跳过不必要的检查和日志级别降低,这个模式只有升级期间使用,日常不要这么启动。
第三步:用catctl并行跑核心升级脚本
cd $ORACLE_HOME/rdbms/admin $ORACLE_HOME/perl/bin/perl catctl.pl -n 8 catupgrd.sql这里的-n 8是并行度。并行度不是越大越好,经验值一般是CPU核数的四分之一到八分之一。因为每个并行进程还会继续派生子进程,并行度太高会把CPU和IO拖垮,反而更慢。如果你的库是CDB,多个PDB的字典升级也会自动排进这个并行流程,所以大库建议把并行度稍微调低,宁可慢一点也要稳。
第四步:重启实例并编译无效对象
SQL> shutdown immediate; SQL> startup; SQL> @?/rdbms/admin/utlrp.sqlutlrp.sql会重新编译升级期间失效的对象,这是19c升级后必跑的动作,一般几十分钟内能完成,具体取决于无效对象数量。
第五步:升级后验证
select * from v$version; select comp_id, comp_name, version, status from dba_registry; select patch_id, action, status from dba_registry_sqlpatch;这三条SQL分别确认数据库版本、组件版本和补丁状态。记得同时看一眼alert_ORCL.log,关注是否有ORA-错误堆栈,尤其是升级过程中出现的ORA-600类错误。
2.3 DBUA图形升级为什么我不推荐
Oracle也提供DBUA图形向导升级,点击下一步就能完成大部分配置。对小库来说DBUA确实省事,但我个人在生产环境升级中不推荐它,原因有三:一是DBUA的并行度和内部调度是黑盒,出了问题不好控制;二是它在交互过程中容易因为环境变量、X11转发等问题中途卡住;三是自动化运维时,命令行方式更容易沉淀成脚本复用。当然,如果你第一次升级、库不大,DBUA完全可以用,只要提前知道它会在后台帮你自动跑preupgrade和catctl,结论是一样的。
3. 升级后高频Q&A:监听、连接、DG这些坑
3.1 监听服务无法启动
这大概是19c升级后出现频率最高的故障。典型症状是lsnrctl start启动后立刻退出,报错常见于TNS-12541、TNS-01189,或者干脆日志里什么都没写。
排查顺序我建议固定下来:
- 看
$ORACLE_HOME/network/admin/listener.ora,重点检查括号是否匹配、HOST那一项是否用了无法解析的主机名; - 在服务器上先
ping 主机名,再cat /etc/hosts确认解析没问题; - 检查1521端口是否被别的进程占用,
netstat -an | grep 1521; - 在listener.ora里临时把HOST改成
localhost或实际IP,排除主机名配置问题。
如果是Windows服务方式启动,还要额外检查服务账户是否有权限、服务是否被残留。19c最常踩的一个坑是老版本卸载不干净,注册表里残存的旧监听服务还在占用端口,这时哪怕配置没问题也起不来。解决办法是删除旧服务后重启Windows再试。
3.2 ORA-28547连接失败
ORA-28547: connection to server failed, probable Oracle Net admin error这个错误我见过两种完全不同的场景。第一种是客户端应用连不上,典型原因是客户端sqlnet.ora里命名方式配置有问题,或者tnsnames.ora连接串写错;第二种是连接到外部过程(External Procedure)时出的,比如调用Java存储过程或某些驱动库。
如果服务端是19c、客户端还是11g的老OCI,很容易在连接阶段直接报ORA-28547。这种情况本质是客户端版本太老,与19c的Oracle Net协议不兼容。解决办法:升级客户端到至少12c以上,或者在服务器端sqlnet.ora里设置SQLNET.ALLOWED_LOGON_VERSION=8往后兼容,但要注意这个设置会降低认证安全性,属于临时方案。另外第三方工具连接不上时,先查驱动版本:DBeaver需要手动配置ojdbc8下载,Navicat则要确认自带的instant client版本足够新。
3.3 DG主备切换遇到resolvable gap
DG切换是老生常谈,但每次都有朋友问“resolvable gap”到底要不要紧张。先说结论:只要备库上能查出v$archive_gap有记录,就说明主备之间确实存在日志缺口,此时不能直接做switchover,必须先补gap。
处理gap的常规动作:
-- 备库查看gap select * from v$archive_gap;拿到缺失的日志序号后,办法有三种:
- 如果gap很小,直接把主库对应归档日志文件拷贝到备库,用
ALTER DATABASE REGISTER LOGFILE '/path/arch/xxx.dbf';注册; - 如果gap较多或者文件很大,用RMAN的
RESTORE ARCHIVELOG从主库拉取缺失归档,比自己手动拷文件更省事; - 如果备库落后实在太多,重建备库反而是最稳的选择,别硬追。
补完gap后执行ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION;恢复应用。特别提醒:一主两备的场景下,切换前必须分别检查两台备库的gap,光确认某一台没用。failover到备库后,原主库要重建为新备库,这个流程提前演练过才不会手忙脚乱。
3.4 进入ASM的常用方式
升级或巡检时经常需要查看ASM磁盘组状态,很多新人卡在中等半天不知道用什么命令。简单说两种最常用的:
# 用grid用户登录,进入SQL*Plus并获取ASM管理权限 sqlplus / as sysasm # 或者在系统层面直接操作ASM文件 asmcmd ls注意ASM的ORACLE_HOME必须指向GI的安装目录,而不是数据库的ORACLE_HOME,否则会提示权限不足或者找不到asmcmd。asmcmd里常用du查看磁盘使用率,find定位文件,cp跨磁盘组拷贝文件,基本满足日常巡检。
3.5 12c删除不干净导致新环境异常
这个问题在Windows上最多见。旧版本Oracle卸载后,注册表、服务项、目录残留都会干扰新的19c安装。常见现象是安装到一半报驱动冲突,或者装完后监听、实例无法启动。
Linux下的清理可以直接手动删:/etc/oratab、/etc/oraInst.loc、/etc/oracle、/opt/ORCLfmap,以及整个ORACLE_BASE目录。Windows下的清理重点是注册表HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE,还有残留的服务,可以打开管理员命令行执行sc delete OracleServiceXXX。这些都属于“一次删干净,省得后面折腾”的典型经验。
4. 升级后附带的高频SQL问题速查
4.1 dual表别拿来当业务表用
群里有人问过“dual表最多能存多大数据”,这个问题本身就反映了对dual表定位的误解。dual是Oracle系统内部提供的一个单行单列表,主要用来执行select sysdate from dual这类不依赖业务表的查询,它不是给你存数据的。虽然在一些特殊版本里你能对dual执行update,但没有意义还会引发奇奇怪怪的并发问题。业务数据老老实实建业务表,dual表的事知道它是“一行一列”就够了。
另外经常有人分不清trunc(sysdate)和to_char(sysdate)的用法。trunc(sysdate)返回的是日期时间,trunc(sysdate)是当天零点,trunc(sysdate, 'MM')是月初,trunc(sysdate, 'YYYY')是年初;而to_char(sysdate, 'yyyy-mm-dd hh24:mi:ss')返回的是字符串,两者用途完全不同,SQL里混用会导致隐式转换问题,影响索引利用。
4.2 case when、逗号拆分、插入不重复
case when是Oracle里最常用的条件表达式,新手最常见的坑是把NULL写进简单case里:
-- 正确写法:搜索case select case when score >= 90 then 'A' when score >= 60 then 'B' else 'C' end as grade from student;注意不要写成case NULL when NULL ...,NULL判断在case里永远不成立,必须用is null或者搜索case写法。
按逗号拆分列为多行也是高频需求。下面这条SQL可以快速实现:
select trim(regexp_substr('a,b,c', '[^,]+', 1, level)) as item from dual connect by level <= length('a,b,c') - length(replace('a,b,c', ',', '')) + 1;“插入数据重复则不插入”这个问题,从MySQL转过来的朋友尤其容易踩坑,Oracle没有MySQL的ON DUPLICATE KEY或ON CONFLICT语法,正确写法是MERGE:
merge into target_table t using (select :id as id, :name as name from dual) s on (t.id = s.id) when not matched then insert (id, name) values (s.id, s.name);并发插入场景下,光靠MERGE还不够,目标表上要有主键或唯一索引兜底,否则两个会话同时判断“不存在”还是会插重。
4.3 MySQL与Oracle的语法差异速查
从MySQL切到Oracle,或者说现在很多库要从MySQL迁移到OceanBase再反推,语法差异是绕不开的。我整理了一份高频差异表:
| 功能 | MySQL | Oracle |
|---|---|---|
| 分页 | LIMIT 偏移, 行数 | ROWNUM 或 OFFSET ... FETCH NEXT |
| 空字符串 | 允许空字符串 | 空字符串''等价于NULL |
| 字符串拼接 | CONCAT(),也支持|| | 建议用||,CONCAT只能接两个参数 |
| 自增列 | AUTO_INCREMENT | 12c开始IDENTITY列,或SEQUENCE配触发器 |
| 日期当前值 | NOW() / CURRENT_TIMESTAMP | SYSDATE / CURRENT_TIMESTAMP |
| 条件函数 | IF(cond, a, b) | CASE WHEN 或 DECODE |
| 布尔类型 | BOOLEAN / TINYINT(1) | NUMBER(1) |
| 禁用索引 | ALTER INDEX ... DISABLE | ALTER INDEX ... UNUSABLE,重建用REBUILD |
| 事务提交 | 默认自动提交(无显式事务时) | 显式COMMIT,未提交不生效 |
| 存储过程抛异常 | SIGNAL SQLSTATE | RAISE_APPLICATION_ERROR |
这里尤其注意分页和空字符串两点。Oracle 19c其实已经支持OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY这种标准写法,比老式的ROWNUM三层嵌套清晰得多:
select * from employees order by employee_id offset 20 rows fetch next 10 rows only;空字符串的问题则是隐蔽的大坑,往Oracle表里插入''和插入NULL效果一样,这会导致很多从MySQL迁过来的应用在非空判断上出现诡异乱象,迁库时一定要扫一遍代码里的空字符串逻辑。
5. 升级避坑经验总结
最后分享几条我这些年做Oracle升级攒下来的经验。
第一,preupgrade.jar的结果一定要逐条读。这个工具判定的每一项风险都是前人踩坑换来的,比如它会提示某些表空间空间不足、某些待升级组件有已知bug、某些初始化参数需要调整。无视这些警告强行升级,后续大概率在某个凌晨两点出事。
第二,升级前准备回滚方案比准备升级方案更重要。RMAN备份是底线,如果库开启了闪回,建议升级前执行一次create restore point before_upgrade guarantee flashback database;,这样升级失败可以快速闪回到升级前状态,比从头恢复备份快太多。注意这个restore point是guarantee类型,会占闪回区空间,确认成功后记得删掉。
第三,不要一发布新RU就冲上去。补丁版本建议选择发布三个月以上的RU,让足够多的环境帮我们“试毒”。热词里出现的19.25.0.0.241015虽然新,但它是2024年10月发布,如今已经跑过相当长时间,属于可以放心用的成熟补丁。如果是刚发布两周的RU,除非有明确bug修复需求,否则别急着在生产上打。
第四,从11g升到19c后,应用侧回归测试的优先级要拉到最高。11g到19c跨越的版本多,隐含的认证变化、SQL执行计划变化、数据库优化器行为变化,都会让原本跑得好好的SQL突然变成慢SQL。升级完成后建议让性能测试环境先跑一轮压测,重点看TOP SQL的前后差异,再决定是否切生产。
第五,顺手把非CDB转成CDB/PDB。19c以后非CDB已经走在下坡路上,与其几次升级都重复改造,不如这次直接一步到位。最简单的方式是通过DBUA在升级时选择转换,或者用DBMS_PDB.DESCRIBE配合插拔PDB的方式完成改造。这条路径确实多花几个小时,但能帮你给未来几年的运维省下大把主动权。
我在实际项目里还有一个固定习惯:每做完一次升级,就把环境检查、升级命令、问题记录整理成一页纸checklist存下来,下次再升级直接照着走,不临时翻文档。尤其这次用catctl并行升级的日志,我会完整保留到归档目录,后面如果出问题排查,这些日志比什么工具都管用。19c升级的难度不在命令本身,而在于细节和顺序,把这篇文章里提到的坑提前排掉,你也能把升级窗口控制得稳稳当当。