简介:这份压缩包面向使用 Oracle 19c 的数据库管理员与运维人员,针对 19.0.0 补丁环境下的时区升级需求,提供 TSTZ 35 版本时区补丁 p31335037 的 Linux x86-64 安装文件,可用于处理 ORA-39405 时区版本不一致报错。补丁包共 10 个文件,以 6 个 dat 数据文件、2 个 xml 配置文件和 2 个 txt 说明文本为主,压缩包约 394KB,结构精简,便于在测试或维护窗口内校验与部署。需要注意,该补丁仅适用于 Oracle 19.3 版本添加 35 版本时区,其他版本使用会报错;可通过查询时区文件版本确认当前状态,并结合 README 等文本了解补丁说明。对需要排查 TSTZ 版本问题、核对补丁适用条件与文件组成的读者而言,可据此确认补丁批次和安装前检查项,减少因版本不匹配导致的重复尝试,更稳妥地安排维护与验证。已有 2570 人学习下载,适合数据库运维、升级迁移和时区告警处理场景参考。
1. 时区补丁不是解压就完事:19c DST v35 装之前先看清三笔账
某开发者半夜被叫起来处理一套 19c 库,报错是跨时区跑批的结果差了三十分钟,翻到最后发现不是业务代码的锅,是数据库里的时区文件版本还停在旧档。这类事在 oracle19c 环境里不算罕见:19.0.0 基线自带的时区文件版本偏老,操作系统那边的 tzdata 早就往前跑了好几轮,补丁 p31335037 干的就是把差距一次性拉到 DST v35。但它不是解压、apply 就收工的补丁——补丁本身只负责把新的时区文件铺进$ORACLE_HOME,数据库内部的时区版本得另走一遍DBMS_DST升级,历史里带时区的那批数据还得先探测再决定怎么处置。顺序装错、版本没对齐,轻则 ORA-39405 在数据泵上反复弹,重则带时区的历史数据算出来是偏的。这套流程最该先看的是正在维护 19c 单实例或 RAC、要跟上下游做时区口径对齐、或者被导入报错逼到墙角的运维。
2. 打补丁之前的账:19.0.0 基线、OPatch 版本与时区文件现状
时区补丁的翻车点,八成不在opatch apply那几秒,而在打之前没把三样东西确认清楚:数据库自己的版本、OPatch 的版本、以及V$TIMEZONE_FILE里现在到底是哪一档。这三样任何一样对不上,后面要么补丁根本装不进去,要么装进去了数据库还没认。
2.1 三个版本号先对齐:数据库、OPatch、timezone file
先登库把当前状态摸一遍,别凭印象。19c 的V$TIMEZONE_FILE只有一行有效记录,VERSION字段就是当前数据库在用的时区文件版本号,这个数字决定了后面DBMS_DST要升到哪个目标版本。
-- 数据库版本,确认是 19.0.0 这一支 select banner_full from v$version; -- 当前时区文件版本,记住这个数字,升级前后都要看 select version from v$timezone_file; -- 顺带确认实例状态与是否 CDB select name, cdb, open_mode from v$database;V$TIMEZONE_FILE返回的VERSION常见是 31 或 32 这一档,具体取决于你手上这套ORACLE_HOME打到了哪个月的 RU。这个值不是补丁一装就自动变的,很多人的误区就在这——补丁把/u01/app/oracle/product/19.0.0/dbhome_1/oracore/zoneinfo/下的timezlrg_35.dat放进去了,但数据库启动时读哪个文件是由数据字典里的版本号决定的,不跑升级流程就还是老版本在生效。
再看 OPatch,这个工具版本太旧会直接导致补丁识别失败:
# OPatch 版本,19c 上常见做法是 12.2.0.1.36 及以上 $ORACLE_HOME/OPatch/opatch version # 已装补丁清单,顺便存一份基线,回滚时要用 $ORACLE_HOME/OPatch/opatch lsinventory -detail > /backup/lsinventory_before_$(date +%F).log如果opatch version低于 12.2.0.1.36,先单独把 OPatch 升上去再谈补丁。OPatch 升级是替换$ORACLE_HOME/OPatch整个目录,替换前把旧目录改名留底,出问题直接改回来就行,这一步比补丁本身好救得多。
2.2 补丁包里到底有什么:p31335037 的组成与安装形态
拿到p31335037_190000_Linux-x86-64.zip之后不要直接解压到$ORACLE_HOME,先看结构。这个包的命名规则本身就带信息:190000表示面向 19.0.0 的ORACLE_HOME,Linux-x86-64表示平台,31335037是补丁号。解压后的目录里除了README.html、etc/config这些标准件,还有一份README.txt,里面写着这个补丁的前置条件和是否需要额外跑脚本,值得花五分钟看一遍。
# 先列出压缩包内容,确认目录层级再解压 unzip -l p31335037_190000_Linux-x86-64.zip | head -20 # 解压到独立的补丁目录,不要放在 ORACLE_HOME 里面 mkdir -p /u01/soft/patches cd /u01/soft/patches unzip /u01/soft/p31335037_190000_Linux-x86-64.zip # 解压后应该能看到 31335037 目录 ls -ld /u01/soft/patches/31335037这个补丁属于时区数据类补丁,安装形态是二进制文件替换,不带 SQL 脚本,所以它不归datapatch管。这一点和 RU 完全不同,很多人习惯性 apply 完就跑datapatch,看到输出里 0 个应用就以为失败了,其实是正常现象。真正的“落地”动作是后面的DBMS_DST升级,这个顺序不能反。
提示:如果这套库是 RAC,补丁要在每个节点的
$ORACLE_HOME上分别 apply;$GRID_HOME不需要动,时区补丁只影响数据库侧。
2.3 备份与回退预案:打之前要留的后路
时区升级是不可逆的——或者说,回退的代价远大于重装。DBMS_DST升级完再想退回旧版本,官方没有提供反向脚本,只能靠备份恢复。所以打之前这几样必须齐全,缺一样我都建议先别动手。
| 备份对象 | 手段 | 用途 |
|---|---|---|
| 数据库全量 | RMANbackup database plus archivelog | 时区升级失败后整库回退 |
$ORACLE_HOME | tar整目录打包 | OPatch 回滚、文件被覆盖后比对 |
| 补丁清单 | opatch lsinventory -detail | 确认回滚后状态回到基线 |
| 受影响数据清单 | DBMS_DST.FIND_AFFECTED_TSTZ结果 | 升级后核对数据是否被正确转换 |
# 1) 数据库全备,带归档 rman target / <<'EOF' backup database plus archivelog; backup current controlfile; EOF # 2) ORACLE_HOME 整目录打包,注意排除临时目录 tar -czf /backup/oracle_home_$(date +%F).tar.gz \ --exclude='*/temp/*' --exclude='*/log/*' \ $ORACLE_HOMEtar这一步看着笨,但时区升级出问题时它是最快的后悔药——$ORACLE_HOME/oracore/zoneinfo/下的.dat文件被覆盖后,直接比对两个版本的文件就能判断补丁到底有没有铺进去,不用靠猜。
3. 应用补丁:OPatch 命令行里每一步的实际含义
补丁应用这一步本身不复杂,复杂的是环境变量没对、服务没停干净、冲突没提前查。我一般把这三件事拆成三段做,每段都要看到预期输出才往下走。
3.1 停库停监听的顺序与环境变量确认
第一条命令永远是确认环境变量,不是 apply。在错误的ORACLE_HOME里打补丁是新手最容易犯的错,尤其是机器上装过多套 19c 的时候——$ORACLE_HOME指向 A 库,$ORACLE_SID连的是 B 库,opatch apply会把补丁铺到 A 库上,然后你在 B 库里怎么查都查不到,白白排查半天。
# 三连确认,任何一个输出不对就停下来 echo "ORACLE_HOME = $ORACLE_HOME" echo "ORACLE_SID = $ORACLE_SID" which opatch # 必须落在 $ORACLE_HOME/OPatch/opatch 下 # 确认 opatch 与当前 ORACLE_HOME 匹配 $ORACLE_HOME/OPatch/opatch lsinventory | head -5确认无误之后再停服务。单实例和 RAC 的停法不一样,RAC 要走srvctl,让集群感知到实例下线,否则补丁打完了实例起不来还得回头清状态。
# 单实例:先停监听,再停实例 lsnrctl stop sqlplus / as sysdba <<'EOF' shutdown immediate; exit EOF # RAC:用 srvctl 停整库,别直接 shutdown srvctl stop database -d ORCL -o immediate srvctl status database -d ORCL # 确认所有实例都已停止停监听这一步在单实例上不是必须的,但我会做——补丁替换期间如果有客户端连进来触发 ORA-01034,日志里会混进无关报错,排查时干扰太大。
3.2 冲突检查与 opatch apply 的参数取舍
在补丁目录里执行冲突检查,这一步比 apply 更重要。它会告诉你这个补丁和已装的哪些补丁有文件重叠,重叠的如果已经被更高版本的补丁覆盖过,apply 会直接拒绝。
cd /u01/soft/patches/31335037 # 冲突检查,-ph 后面跟补丁目录 $ORACLE_HOME/OPatch/opatch prereq CheckConflictAgainstOHWithDetail -ph ./输出里如果只看到Prereq "checkConflictAgainstOHWithDetail" passed,就可以往下走。如果报Patch 31335037 conflicts with patch XXXXXXXX,先看是谁覆盖谁——时区补丁的文件是oracore/zoneinfo/下的固定文件名,如果之前打过更高版本的时区补丁(比如 DST v36 及以上),这个补丁就是降级,opatch 会拦住。
# 正式应用,默认交互式确认 $ORACLE_HOME/OPatch/opatch apply # 无人值守场景(比如批量节点),加 -silent $ORACLE_HOME/OPatch/opatch apply -silent交互式会列出补丁信息、ORACLE_HOME路径、受影响组件,最后让你输入y。我一般不用-silent,因为这一步的输出是判断补丁有没有正确识别的关键——如果Components一栏里没有oracle.server或版本对不上,说明补丁包和ORACLE_HOME不匹配,此时Ctrl+C还来得及。
apply 完成后有两件事必须确认:
# 1) 补丁已登记 $ORACLE_HOME/OPatch/opatch lspatches | grep 31335037 # 2) 时区文件已铺到位 ls -l $ORACLE_HOME/oracore/zoneinfo/ | grep -E 'timezlrg_35|timezone_35'第二条是关键。opatch lspatches里有记录只说明补丁注册成功,timezlrg_35.dat存在才说明文件真的铺进去了。两个文件类别要分清:timezone_35.dat是精简版(只含有限的时区),timezlrg_35.dat是大文件版,19c 默认用的是后者。
3.3 datapatch 跑一遍的意义与启动顺序
前面说过时区补丁不带 SQL 脚本,但datapatch我还是会跑一次,目的不是打补丁,是确认没有遗留的 SQL 补丁没应用——有时候机器上前一次 RU 打了一半没跑datapatch,趁重启一起收掉。
# 先把库启到 open 状态,datapatch 需要实例可连 srvctl start database -d ORCL # RAC # 或单实例 sqlplus / as sysdba <<'EOF' startup; exit EOF lsnrctl start # 跑 datapatch,看输出里 Applied 数量和 Errors 数量 $ORACLE_HOME/OPatch/datapatch -verbose时区补丁场景下,正常输出里SQL Patching部分应该是 0 个补丁被应用,Errors为 0。如果Errors大于 0,先别急着往下走时区升级,datapatch的日志在$ORACLE_BASE/cfgtoollogs/sqlpatch/下面,按时间戳找最新的那个目录,里面有具体报错。
CDB 环境下datapatch会自动处理所有 PDB,但输出里每个 PDB 都会单独列一行,要逐个看有没有失败。我见过一次PDB$SEED报错导致整个datapatch中断,后面的 PDB 全没跑,Errors只显示 1,很容易漏看。
到这一步,补丁在文件层面和注册层面都已经到位,但数据库的时区版本还停在老档。真正的重头戏在下一章。
4. 升级时区文件:DBMS_DST 从旧版本走到 35 的完整流程
DBMS_DST这套流程分三段:探测、升级、收尾。探测阶段不改任何业务数据,只统计哪些行会被时区偏移影响;升级阶段才是真正动数据。中间那张错误表是整件事的黑匣子,探测和升级都要靠它定位问题行。
4.1 先探测:哪些数据会被时区升级碰到
探测前先确认当前版本,BEGIN_PREPARE的参数必须是目标版本 35,写错了会创建一个目标不对的升级窗口,后面再执行就得先清理再重来。
-- 记录基线 select version from v$timezone_file; -- 开启探测窗口,参数是目标时区版本 exec dbms_dst.begin_prepare(35);begin_prepare会创建几张内部表,其中sys.dst$error_table用来记录升级时会出问题的行。接着调探测接口:
declare n number; begin dbms_dst.find_affected_tstz(affected_rows => n); dbms_output.put_line('受影响行数: ' || n); end; / -- 探测结束,关掉准备窗口 exec dbms_dst.end_prepare;find_affected_tstz只统计TIMESTAMP WITH TIME ZONE和TIMESTAMP WITH LOCAL TIME ZONE两类列里、转换后偏移量会变的行。affected_rows输出大于 0 是正常的,不必紧张——只要这些行转换后是“正确的”,就只是展示值变化。真正要关注的是那些本身就存了错时区的行,它们才是业务口径对不上的源头。
如果想知道具体是哪些表哪些行,查错误表:
select * from sys.dst$error_table where rownum <= 20;这个表里有OWNER、TABLE_NAME、COLUMN_NAME、ROWID这些信息,可以直接拿ROWID回原表定位。探测阶段没写进错误表的行,升级时也不会写,所以这一步的结果就是后续核对的清单。
注意:
BEGIN_PREPARE之后必须END_PREPARE,否则会留下半开的升级窗口,下次再执行BEGIN_UPGRADE会报 ORA-56930 之类的错,得先手工清理。
4.2 正式升级:startup upgrade 到 end_upgrade 的完整顺序
正式升级要重启到 upgrade 模式,这一点和普通补丁流程不一样,容易漏。顺序错一步,BEGIN_UPGRADE会报“数据库未处于可升级状态”。
-- 1) 关库 shutdown immediate; -- 2) 以 upgrade 模式启动 startup upgrade; -- 3) 开启升级窗口,参数是目标版本 35 exec dbms_dst.begin_upgrade(35);begin_upgrade会把数据库切到受限状态,只有SYSDBA能连,业务连接会被挡在外面。这也是为什么时区升级必须约窗口——它不是 online 操作。
-- 4) 执行实际的数据升级,耗时取决于带时区数据的量 exec dbms_dst.upgrade_database;upgrade_database这一步会遍历所有带时区列的表,把旧时区规则下的存储值重新计算一遍。表多、行多的时候这一步可能跑几十分钟,不要中途Ctrl+C,中断后恢复的成本很高。
-- 5) 结束升级,写回数据字典 exec dbms_dst.end_upgrade; -- 6) 重启到正常模式 shutdown immediate; startup;end_upgrade做完之后,V$TIMEZONE_FILE里的VERSION会变成 35。如果还是旧数字,只有两种可能:补丁文件没铺进去,或者升级流程没走完。前者回第 3 章查timezlrg_35.dat是否存在,后者查sys.dst$upgrade_history里有没有对应的升级记录。
如果探测阶段发现受影响行数很大、升级窗口不够,可以分批处理:先用DBMS_DST.UPGRADE_DATABASE里的parallel参数(部分版本支持)提高并发,或者按 schema 分批。19c 上更常见的做法是把窗口拉长,一次性做完,因为分批升级会反复创建和关闭窗口,出错概率反而高。
4.3 PDB 与 Data Pump:多租户下要单独走一遍
CDB 环境下的时区升级不是一条命令搞定。CDB$ROOT和每个 PDB 都要走同样的流程,而且顺序有讲究:先CDB$ROOT,再各个PDB,PDB$SEED不能升级——它是模板,版本跟着新创建的 PDB 走。
-- 先升根容器 alter session set container = CDB$ROOT; exec dbms_dst.begin_upgrade(35); exec dbms_dst.upgrade_database; exec dbms_dst.end_upgrade; -- 再逐个 PDB,注意每次都要 switch container alter session set container = PDB1; exec dbms_dst.begin_upgrade(35); exec dbms_dst.upgrade_database; exec dbms_dst.end_upgrade;每个 PDB 的执行必须在SYSDBA连到对应容器下进行,alter session set container之后要重新确认当前容器是不是对的。曾经有人在CDB$ROOT里连续执行了三次begin_upgrade,以为升了三个 PDB,结果只在根容器上重复开窗,PDB 一个没动,后面数据泵导入直接撞上 ORA-39405。
Data Pump 这条线必须单独提:19c 的expdp/impdp在导出时会把源库的时区版本一起记进 dmp 文件,导入到目标库时如果目标库时区版本低于源库,直接报 ORA-39405 拒绝导入。解决方式只有两种——把目标库升到同版本或更高,或者用impdp的ignore参数(不推荐,数据口径会歪)。所以如果这套库是某个数据流转链路的目标端,升级时区不是选择题,是前置条件。
5. 避坑与排查:时区补丁最常见的几处翻车
下面这几条是按报错出现频率排的,从补丁应用阶段到业务暴露阶段都有。每条按现象、原因、解决三段写,照着对号入座就行。
5.1 补丁应用阶段的三个坑
坑一:补丁应用成功,但V$TIMEZONE_FILE没变。
现象:opatch lspatches能看到 31335037,timezlrg_35.dat也在目录里,但查V$TIMEZONE_FILE还是旧版本。原因:补丁只负责铺文件,数据库内部版本要在DBMS_DST升级后才更新,这两件事是分开的。解决:确认timezlrg_35.dat存在后,按第 4 章的流程走一遍begin_upgrade到end_upgrade,重启后再查。
坑二:opatch apply报Patch 31335037 is not applicable to the current OH。
现象:补丁包明明是对着 19.0.0 下载的,apply 时却报不适用。原因通常是三个:$ORACLE_HOME指向了别的版本;OPatch 版本太低识别不了补丁元数据;补丁包下载或解压时损坏。解决:先echo $ORACLE_HOME确认路径,再opatch version确认版本,最后拿unzip -t校验压缩包完整性。三个都正常还报错,去看README.txt里的Prerequisites,有些补丁要求先装某个前置补丁。
坑三:补丁回滚后实例起不来。
现象:opatch rollback -id 31335037执行成功,但startup报文件缺失。原因:回滚把timezlrg_35.dat删了,但数据库的时区版本已经被DBMS_DST改到 35,启动时找不到对应的.dat文件。解决:时区升级之后想回滚补丁,必须先恢复数据字典到旧版本——实际做法是用 2.3 里的 RMAN 备份整库恢复,单独 rollback 补丁是不够的。这也是我强调时区升级前必须整库备份的原因。
5.2 时区升级阶段的坑
坑四:DBMS_DST.FIND_AFFECTED_TSTZ报 ORA-56930 或找不到错误表。
现象:执行探测时报“升级窗口已打开”或“错误表不存在”。原因:上一次BEGIN_PREPARE没正常结束,留下了半开的窗口;或者sys.dst$error_table被手工删过。解决:先查select * from sys.dst$upgrade_history确认有没有残留记录,有的话执行exec dbms_dst.end_prepare关掉窗口,再重新begin_prepare。错误表被删的情况只能重建,建议直接用begin_prepare让系统自己建。
坑五:升级过程中报 ORA-01805,提示数据库时区文件版本不一致。
现象:upgrade_database执行到中途报 ORA-01805,或者完成后部分查询继续报。原因:ORA-01805 的意思是客户端、数据库、时区文件三者版本对不上,通常发生在升级过程中数据库已经切到 v35、但客户端还在用旧版本的tzdata。解决:先确认V$TIMEZONE_FILE已经是 35,再更新应用服务器的时区库;如果是 JDBC 客户端,检查驱动版本和oracle.jdbc.timezoneAsRegion参数。
5.3 业务暴露阶段的坑
坑六:升级后报表时间整体偏了半小时。
现象:升级完成后,某些报表的时间列显示比预期早或晚 30 分钟。原因:这些列存的TIMESTAMP WITH LOCAL TIME ZONE值在写入时就没按规范传时区,数据库按会话时区做了隐式转换,升级后规则变了,旧值的解释跟着变。解决:用 4.1 探测阶段留下的sys.dst$error_table定位具体表和行,逐个核对业务语义,该修数据的修数据,该改应用写法的改写法。这类问题不是补丁造成的,是原来就埋着的,升级只是把它翻出来了。
坑七:数据泵导入报 ORA-39405,目标库拒绝接收。
现象:impdp一开始就失败,提示源库的 TSTZ 版本高于目标库。原因:迁移链路里源端已经升到 v35,目标端还在 v32 或更低。解决:把目标库也升到 v35,或者先从源端导出时用expdp的version参数降级(只能降到数据库版本支持的最低值,时区版本管不了)。实际做法是两端对齐,别指望绕过。
6. 装完怎么验证:三条 SQL 加一次时间穿越测试
时区补丁的“装完”不能只看 OPatch 的输出,得让数据库自己证明它在用新规则。我固定用下面这组检查,前后不到两分钟,但能覆盖版本、转换、边界三件事。
-- 1) 时区文件版本,必须等于 35 select version from v$timezone_file; -- 2) 当前会话的数据库时区,确认没被应用层改歪 select dbtimezone, sessiontimezone from dual; -- 3) 一次跨时区转换,看结果是否与预期偏移一致 select from_tz(timestamp '2021-07-01 12:00:00', 'Asia/Shanghai') at time zone 'America/New_York' as ny_time from dual;第 3 条的结果应该是纽约当天 00:00:00(夏令时期间 UTC-4)。如果算出来是 23:00 或 01:00,说明还在用旧时区规则,回头查V$TIMEZONE_FILE。
光有常规转换还不够,真正能验证 DST 规则是否正确的是“时间穿越”——挑一个夏令时切换的边界时刻做转换。美国 2021 年 3 月 14 日凌晨 2 点直接跳到 3 点,这个不存在的时刻是检验时区文件版本的最好样本:
-- 不存在的本地时刻,新版本会按规则做偏移处理 select from_tz(timestamp '2021-03-14 02:30:00', 'America/New_York') at time zone 'UTC' as utc_time from dual; -- 对照系统时间,确认服务器时区库与数据库一致| 验证项 | 检查手段 | 期望结果 |
|---|---|---|
| 补丁注册 | opatch lspatches | grep 31335037 | 有记录 |
| 文件落盘 | ls $ORACLE_HOME/oracore/zoneinfo/timezlrg_35.dat | 文件存在 |
| 数据库版本 | select version from v$timezone_file | 35 |
| 常规转换 | 上海 12:00 转纽约 | 00:00(夏令时) |
| 边界转换 | 纽约 02:30 转 UTC | 不报错,按规则偏移 |
| 数据泵兼容 | 目标库V$TIMEZONE_FILE | 不低于源库 |
最后一条容易被忽略:如果这套库是数据流转链路的一环,验完自己还得去看一眼上下游的时区版本。某次我这边升完 v35,第二天上游推过来的 dmp 就导不进去了,查下来是上游还在 v32,对方的补丁窗口排在两周后。从那以后我每次升时区,都会先把链路两端select version from v$timezone_file的结果截个图发到变更群里,确认对端也排上了窗口才动手。这套 p31335037 的 Linux x86-64 包我一般和README.txt、补丁号对照表放在一个目录里,下次再遇到 19c 的时区问题,按文件名p31335037_190000_Linux-x86-64.zip找过去就能直接开工,希望帮到你。
本文还有配套的精品资源,点击获取