ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Oracle 19c时区补丁p31335037与DBMS_DST升级指南

Oracle 19c时区补丁p31335037与DBMS_DST升级指南

简介:面向Oracle 19c(19.3)数据库管理员与运维人员,这份Linux x86-64平台时区补丁用于将数据库TSTZ时区升级到35版本,重点处理ORA-39405时区版本不一致所引发的数据导入、升级迁移及兼容性报错。补丁仅适配Oracle 19.3,其他版本使用容易报错,包内说明也提示需先确认当前时区版本和安装前提,避免误用。压缩包为zip格式,共10个文件,其中6个dat数据文件、2个xml配置或元数据文件、2个txt说明文本,整体约394KB,体积轻便,便于在测试环境验证后用于对应版本维护。已有2570人学习下载,说明该补丁在Oracle时区问题处理场景中关注度较高。对需要补齐TSTZ 35时区、排查ORA-39405错误的19.3用户而言,可借助包内补丁文件与说明完成版本核对和补丁应用,降低时区版本不匹配导致的业务中断风险,并为后续升级与数据兼容处理提供可回溯的维护依据。

1. 一次跨区域报表的时间偏了一小时

周五下午接到反馈:跨区域的订单时间整体偏了一小时,应用团队查了三天 SQL 说没问题,最后定位到数据库里的时区文件版本还停在 v32——oracle19c 时区35 补丁 p31335037 一直躺在下载目录里没打。这类问题不报错,数据看着也像对的,只在规则切换那个窗口里露馅。p31335037_190000_Linux-x86-64.zip 就是给 19.0.0 的 Linux x86-64 库补 DSTv35 用的:先在 Oracle Home 上打补丁,再用 DBMS_DST 把库的文件版本推到 35,TZ_OFFSET、FROM_TZ和 TSTZ、TSLTZ 列的换算才会按新规则走。适合手上管着 19c 单机或 RAC、库里有TIMESTAMP WITH TIME ZONE列、并且确实踩到规则变更窗口的同行。下面按我实际执行的顺序拆开说。

2. 两个层次别搞混:Home 补丁、文件版本与 DBTIMEZONE 的边界

2.1 补丁包里到底装了什么

p31335037_190000_Linux-x86-64.zip解压出来是一个以补丁号命名的目录,标准 OPatch 结构:README.txt、etc/config/inventory.xml、files/三块。真正落到 Home 里的东西主要是一批 zoneinfo 数据文件,路径在$ORACLE_HOME/oracore/zoneinfo/下,命名规律是timezone_35.dat和timezlrg_35.dat——前者不带闰秒信息,后者带,数据库默认走带闰秒的那份。除此之外,补丁还可能会更新与规则计算相关的二进制和共享库,所以它不能靠“手工拷两个 dat 文件”替代,必须走 OPatch。

文件名里的190000是版本标签,含义是这份补丁针对 19c 基线构建。已经打过 Release Update 的 Home 能不能装,不要凭文件名判断,判断依据只有一条:opatch prereq CheckConflictAgainstOHWithDetail的检查结果和README.txt里写的前置条件。另外Linux-x86-64是硬约束,同一个补丁号在其它平台上是另一个包,别跨平台搬。

README.txt是这份资源里最该先读的文档,它会告诉你三件事:最低 OPatch 版本要求、是否与已装的 one-off 冲突、以及这个版本覆盖了哪些规则调整。至于具体影响哪些区域、从哪个日期开始生效,以 README 和官方时区变更说明为准,别靠记忆或者网上二手转述,这块的传闻特别多。

2.2 为什么 opatch apply 之后版本号不会自己变

这是最容易翻车的地方,也是被问得最多的一个问题。Oracle 的时区治理分成两层,两层是解耦的:

第一层是 Home 层。打完补丁,Home 就“认识”了 35 这个版本,磁盘上有了对应文件,但数据库一个字都不改。

第二层是库层。数据库实际用哪个版本的规则,由数据字典里的记录决定,查v$timezone_file就能看到当前值。它跟你打了什么补丁没有直接关系,只跟有没有执行过库级升级有关。

所以打完opatch apply,v$timezone_file显示的还是老版本,这是正常现象,不是补丁没装上。想确认补丁,用opatch lspatches看;想确认规则版本,用v$timezone_file看。两条线要分开验。

还要再区分三个容易混的概念:

概念查法影响范围
时区文件版本v$timezone_file规则计算、TSTZ 与 TSLTZ 列的存储与回显
数据库时区SELECT dbtimezone FROM dualTSLTZ 列的归一化基准
会话时区SELECT sessiontimezone FROM dual当前会话里 TSLTZ 的显示,客户端属性

打时区补丁不改变 DBTIMEZONE,也不改变任何会话的 SESSIONTIMEZONE。业务方看到时间不对,很多时候是会话时区配成了固定偏移(比如+08:00),固定偏移天生不参与规则计算,补丁打到最后也修不了它。

2.3 动手前的四项体检

正式操作前,我会在库上跑一遍下面这段,把现状留档。出问题时这份输出就是对照基线。

-- 1) 当前库在用的时区文件版本;19c 里一行一个 container SELECT con_id, filename, version FROM v$timezone_file ORDER BY con_id; -- 2) 有没有一次没走完的升级残留 SELECT name, value$ FROM sys.props$ WHERE name IN ('DST_PRIMARY_TT_VERSION','DST_SECONDARY_TT_VERSION'); -- 3) 库默认时区与兼容参数 SELECT dbtimezone FROM dual; SELECT name, value FROM v$parameter WHERE name = 'compatible'; -- 4) 哪些列会被重写:所有 TSTZ / TSLTZ 列 SELECT owner, table_name, column_name, data_type FROM dba_tab_columns WHERE data_type IN ('TIMESTAMP WITH TIME ZONE', 'TIMESTAMP WITH LOCAL TIME ZONE') AND owner NOT IN ('SYS','SYSTEM','SYSMAN','DBSNMP','XDB','MDSYS', 'CTXSYS','ORDSYS','WMSYS','LBACSYS','AUDSYS', 'GSMADMIN_INTERNAL','OJVMSYS') ORDER BY 1, 2, 3;

第一条给出“现在是什么版本”,第二条给出“有没有半截活”。DST_PRIMARY_TT_VERSION是当前生效的文件版本,DST_SECONDARY_TT_VERSION正常应该是 0;非 0 说明上一次升级没走完,数据库正处在一个过渡态,这时候不能直接再来一遍BEGIN_UPGRADE,要先把上次收尾。这个值是我排查问题时必看的一个黑匣子指标。

第四条那个查询是估工作量的关键。dba_tab_columns扫出来多少行,直接决定第 4 章END_UPGRADE要跑多久。业务表上如果有几十个 TSTZ 列、数据量上亿,维护窗口要按小时算,不要按分钟算。另外compatible按官方说明要满足 DBMS_DST 的要求,19c 默认值一般没问题,但从老版本一路升上来的库要单独确认。

3. 把 p31335037 打进 Oracle Home:预检、冲突检查与验证

3.1 OPatch 版本与环境预检

所有命令的前提是环境变量指对了 Home。机器上同一个用户下有多套 Home(比如老库和新库并存)时,opatch认错 inventory 是血泪经验级别的常见事故,敲之前先echo $ORACLE_HOME看一眼。

# 必须指向要打补丁的那个 Home,别在 root 的默认环境里直接敲 export ORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1 export ORACLE_SID=orcl export PATH=$ORACLE_HOME/OPatch:$ORACLE_HOME/bin:$PATH # 1) OPatch 版本,README 里会写最低要求,低于它就先换 OPatch opatch version # 2) 打之前把已装补丁存一份,出问题好对比差异 opatch lspatches > /tmp/lspatches_before_$(date +%Y%m%d).txt

opatch version输出的版本号要和 README 里的最低要求对齐。OPatch 本身是可以单独升级的工具,不达标就别硬上,先换 OPatch 再打补丁。第二行那个快照看着可有可无,真出问题时它就是“打之前长什么样”的唯一证据。

机器上如果存在多套 inventory(典型场景是一个用户装了两代数据库),opatch可能读到错的oraInst.loc,报错信息通常是找不到 inventory 或者找不到 Home。这种时候用-invPtrLoc显式指定 inventory 文件路径,别去改系统级的默认配置。

3.2 解压、冲突检查与 apply 的完整命令序列

cd /tmp/dst unzip -q p31335037_190000_Linux-x86-64.zip cd 31335037 ls # 预期看到 README.txt etc/ files/ # 先读前置条件这一段,别整篇跳过 sed -n '1,80p' README.txt

解压目录名就是补丁号,不要改名,OPatch 会按目录名去匹配补丁 ID。unzip -q只是省掉滚动输出,出问题时去掉-q看完整解压列表,确认files/下有内容——有些传输损坏的包解压不报错但文件是缺的。

# 冲突检查:这一步不过,后面全白搭 opatch prereq CheckConflictAgainstOHWithDetail -ph ./ # 空间检查:zoneinfo 文件不大,但补丁流程会临时占空间 opatch prereq CheckSystemSpace -ph ./

-ph ./表示以当前目录作为补丁目录。冲突检查的结论只有两种:通过,或者列出冲突的补丁号和原因。碰到冲突不要绕过,去看 README 里的“supersedes / requires”段落,确认是该先回滚旧补丁,还是这个补丁本身已经被更新的包取代了。绕过冲突检查强打,后面库起不来的时候很难收场。

# 同一 Home 下的实例先停干净 # RAC:srvctl stop database -d <db_unique_name> # 单机:sqlplus / as sysdba 里 shutdown immediate opatch apply

停实例这一步的必要性看 README。如果它明确标注了可以在线或逐节点安装,那可以按说明走;没标注就老老实实全停。补丁会替换$ORACLE_HOME下的二进制和库文件,实例还在跑的时候替换,正在运行的进程会拿到一个不一致的 Home,症状是莫名其妙的 ORA- 报错。

opatch apply是交互式的,会问你确认。第一次打建议看完整输出,不要一上来就-silent。脚本化批量打的时候再加-silent,但要配合日志检查:$ORACLE_HOME/cfgtoollogs/opatch/下每次都会留一份opatch<时间戳>.log,报错码是 41 还是 73 这类,最终原因都在日志里,控制台那几行只是提示。

3.3 打完之后的验证与“看起来没生效”的排查

# 补丁是否在列 opatch lspatches | grep -i 31335037 # zoneinfo 文件是否落盘 ls -l $ORACLE_HOME/oracore/zoneinfo/timezlrg_35.dat

两条都有输出,Home 层就算完成了。注意opatch lspatches有时会把补丁号显示成31335037;Database Time Zone File Update这种带描述的形式,用grep匹配纯数字就行。

接下来去库上验:启动实例,查v$timezone_file。如果这里显示的还是老版本,回到 2.2 节——这是设计如此,不是没装上。库层升级是第 4 章的事。

还有一种情况:opatch lspatches有输出、文件也在,但去查$ORACLE_HOME/lib下某个库的修改时间没变。这说明这个补丁只更新了数据文件,没有动二进制。这是正常的,不代表补丁残缺,具体改了什么看files/目录结构和 README 的描述。

4. 库级升级:用 DBMS_DST 把文件版本推到 35

4.1 prepare 窗口:先只扫描不改数据

DBMS_DST.BEGIN_PREPARE是只读的演练。它会扫库、把受影响的表登记到sys.dst$affected_tables,把算不过去的值丢进sys.dst$error_table,但一行数据都不动。生产库我建议一定要跑这一步,原因很实在:真正的升级窗口只有几个小时,而扫描可能要跑很久,把“找问题”和“改数据”拆开,窗口里只做改数据这一件事。

conn / as sysdba -- 只扫描,不改数据。第二个参数 TRUE 表示按原子升级的口径筛表 EXEC DBMS_DST.BEGIN_PREPARE(35); -- 会被重写的表清单,这份结果直接决定窗口要多长 SELECT * FROM sys.dst$affected_tables; -- 已经算不出来的值,重点看这张 SELECT * FROM sys.dst$error_table; -- 有触发器会被暂时禁用的表 SELECT * FROM sys.dst$trigger_table; EXEC DBMS_DST.END_PREPARE;

BEGIN_PREPARE(35)里的 35 是目标文件版本,必须是 Home 里真实存在的版本。如果这会话报错说找不到对应版本,回头确认 3.3 节的 zoneinfo 文件是否落盘,以及是不是在错误的 Home 下执行。

sys.dst$affected_tables会列出所有含 TSTZ 列的普通表,行数可能和你 2.3 节手工查出来的对不上——手工查会把视图、物化视图、分区子表的列也算进去,DBMS_DST的口径更贴近实际会重写的对象。以它为准。

sys.dst$error_table空着是最理想的。如果有内容,每条就是一行“用新规则换算会产生歧义或不存在的时间”的记录,通常会指出owner/table/rowid。这些记录在 prepare 阶段就先修,不要拖到升级窗口里现修,窗口里你既没有时间也没有心情。

4.2 BEGIN_UPGRADE 到 END_UPGRADE 的执行序列

-- 确认只有自己这一个用户会话,BEGIN_UPGRADE 会清场 SELECT COUNT(*) FROM v$session WHERE type='USER' AND username IS NOT NULL; -- 这一步会自己把实例重启进 upgrade 模式,会话会断 EXEC DBMS_DST.BEGIN_UPGRADE(35);

BEGIN_UPGRADE执行后实例重启,重启完库处于 upgrade 模式。这个模式下只有 SYSDBA 能连,普通业务账号连不上——这不是故障,是设计。重连之后先查版本:

SELECT con_id, filename, version FROM v$timezone_file ORDER BY con_id; -- 这时 version 应已经是 35 -- 数据重写的主体在 END_UPGRADE,大库会跑很久,别中途 Ctrl+C EXEC DBMS_DST.END_UPGRADE(35); -- 退回正常模式 SHUTDOWN IMMEDIATE; STARTUP;

END_UPGRADE(35)是整套流程里最耗时的一步,它负责把受影响的 TSTZ 和 TSLTZ 列按新规则重写、清掉 secondary 版本标记、把库退回可正常使用的状态。执行期间不要开并行会话去查这些表,会卡。这个阶段磁盘 IO 和 UNDO 增长都很快,事先确认 UNDO 表空间够用。

如果END_UPGRADE中途报错,别急着SHUTDOWN ABORT。库还在 upgrade 模式,先看sys.dst$error_table,把出问题的行按业务确认后修正(常见做法是改成固定偏移形式,或者挪到有效的本地时间点),然后重跑一次END_UPGRADE(35)。修完再跑的成功率很高,因为出错的行已经被处理掉了。

还有一条边界:如果环境不允许BEGIN_UPGRADE自动重启实例(比如受管控平台的启动脚本限制),可以走手工路线——自己用STARTUP UPGRADE把库拉起来,再调DBMS_DST.UPGRADE_DATABASE。这条路线可控性更强但坑更多,第一次做不建议用。

4.3 多租户、RAC 与 Data Guard 的差异

多租户库只在 CDB$ROOT 执行一次。所有 PDB 共用同一份 zoneinfo,不需要逐 PDB 跑DBMS_DST。19c 的v$timezone_file带CON_ID,升级完成后进各个 PDB 查一遍,版本应该一致;如果有 PDB 显示不同版本,说明有 PDB 在升级期间处于异常状态,需要单独处理。

RAC 分两段看。Home 补丁每个节点都要打,如果 README 支持逐节点安装,可以-local一个节点一个节点来,减少停机;不支持就整库停。库级升级只在其中一个实例上跑一次,BEGIN_UPGRADE会把整个数据库重启,不是单节点重启。打完以后在任何节点上查v$timezone_file结果都应该一样,不一样就是有节点没打上补丁,或者实例用的是另一个 Home。

Data Guard 场景,主备两个 Home 都要打补丁,顺序是先备后主或者按你的标准变更流程走。库级升级在主库执行,变更通过 redo 传到备库。升级期间建议确认备库的日志应用是正常的,不要出现主库升完了备库还卡在半路的情况。全部做完之后,主备各查一次v$timezone_file和DST_SECONDARY_TT_VERSION,两边必须一致,这是切换前的基本检查项。

5. 避坑与排查:五个真实踩过的坑

5.1 补丁阶段的坑

现象一:opatch apply报冲突,提示某个 already installed 的补丁与它改了同一批文件。

原因通常是这个 Home 之前打过更早版本的时区补丁,两个包的files/里存在同名 zoneinfo 文件;也可能是别的 one-off 顺带带了旧版 zoneinfo 文件。解决方式不是删文件硬打,而是在CheckConflictAgainstOHWithDetail的输出里找到冲突补丁号,对照 README 判断是应该先opatch rollback -id <旧补丁号>回滚旧的时区补丁,还是这个新

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进