Oracle时区文件版本升级:ORA-01805排查与DBMS_DST脚本实践
简介:面向 Oracle DBA 与数据库运维人员的时区版本升级脚本包,用于将数据库时区版本调整至较新版本,通常配合官方时区补丁使用。包内共 4 个 SQL 文件,压缩包约 16KB,体量轻便,主要包含升级前检查、时区调整应用以及相关统计辅助脚本,并附有执行顺序说明:先运行检查脚本评估环境,再执行应用脚本完成调整,便于在变更前确认兼容性与影响范围。已有 1693 人学习,适合需要处理时区版本不一致、补丁配套升级或升级前预检的运维场景。脚本以 SQL 形式组织,方便在 SQL*Plus 等环境中按说明逐步执行,可作为时区版本调整的辅助工具,帮助读者快速完成环境核查、变更实施与结果验证,降低手工操作遗漏风险。
1. 凌晨两点的 ORA-01805,多半是时区文件版本没对齐
去年冬天一个报表库半夜跑批失败,报ORA-01805: possible error in date/time operation,诡异的是同一条 SQL 在数据库服务器上手工执行正常,应用连上来就报错。查了两个小时才定位:数据库用的时区文件和应用服务器上客户端带的时区数据差了三四个版本,两边对同一个TIMESTAMP WITH TIME ZONE值的偏移解释不一致,SQL 里只要带AT TIME ZONE、FROM_TZ或者跨时区比较就必然报错。
这类问题不是改 SQL 能解决的,动的是数据库的时区版本(time zone file version)。手工做一遍要走BEGIN_PREPARE → UPGRADE_DATABASE → END_PREPARE → BEGIN_UPGRADE → UPGRADE_DATABASE → END_UPGRADE六个动作,中间还得配合只读窗口、错误表排查、版本校验,漏一步就白跑。这份 DBMS_DST_scripts V1.9 干的就是把这一串按顺序固化成可重复执行的脚本包。
它适合手里有含 TSTZ 列的老库、要在可控停机窗口里做一次时区版本抬升的运维和 DBA;也适合第一次做这件事、想先照着一份能跑的流程走一遍的人。如果库里一个 TSTZ 列都没有,那用不上它,换掉 tzfile 重启就完事。
2. 拆包看结构:V1.9 封的是三段窗口和两张错误表
2.1 时区文件是张映射表,升级动的是存量数据的偏移解释
先把概念掰清楚,不然容易把时区版本升级当成改参数。
Oracle 的时区规则放在$ORACLE_HOME/oracore/zoneinfo/下,文件名形如timezlrg_N.dat、timezone_N.dat,N 就是版本号。这个文件里存的不是"北京时间等于 UTC+8"这种常量,而是一张"地区 → 历史偏移规则"的映射表,包含历次夏令时的起止时间点。某个地区调整过一次切换日期,就要出一个新版本。
v$timezone_file告诉你当前数据库正在用第几版;v$timezone_file_supplied列出这套 ORACLE_HOME 里实际放了几份、都到第几版。两个视图一对,你就知道能不能升、能升到几。
关键在于 TSTZ 列的物理存储:内部记的是 UTC 时刻加一个偏移量,不存地区名。数据库没有"这条数据属于哪个地区"的信息,一旦规则被修正,旧数据里那个裸偏移量就没有办法再反推回正确的绝对时刻。所以升级动作本质是:按新规则把每一行 TSTZ 值重算一遍,写回新的内部表示。这也解释了为什么改DBTIMEZONE、ALTER SESSION SET TIME_ZONE全都没用——它们只影响显示,不碰存量数据。
一个直白的例子:某地区把夏令时起点从 3 月最后一个周日改到第二个周日,落在这一小时里的历史数据,按旧规则反推的 UTC 时刻会比正确值差 3600 秒。升级之后这批行的显示值可能平移一小时,这是正常现象,不是脚本写错了。
2.2 三条路径,选错会白白多停两小时
脚本包一般会把三种做法都留出入口,按库的体量选:
| 路径 | 动作序列 | 停机特征 | 适用场景 |
|---|---|---|---|
| 单段式 | BEGIN_UPGRADE→UPGRADE_DATABASE→END_UPGRADE | 全程停机,时长约等于全量扫 TSTZ 表 | 库不大、TSTZ 表少、能停够时间 |
| 两段式 | BEGIN_PREPARE→UPGRADE_DATABASE(试跑)→END_PREPARE→BEGIN_UPGRADE→UPGRADE_DATABASE→END_UPGRADE | 试跑段不停机,正式段停机 | 生产库、表多、不敢一次压上 |
| 只换 tzfile | 替换oracore/zoneinfo下文件后重启 | 一次重启 | 库内无 TSTZ 数据,只需版本对齐 |
两段式是我默认选的,理由不是"更稳"这种空话,而是试跑段能提前把所有转换异常的行炸出来。UPGRADE_DATABASE在 prepare 窗口里跑完不落盘,END_PREPARE会把改动回滚,代价只是多扫一遍表的 IO 和时间。生产库上,这一遍能换到"正式窗口里不会因为脏数据中途停",很划算。
第三个路径容易被误解:只换 tzfile 只让二进制认识新规则,存量 TSTZ 数据仍按旧规则存着,客户端版本对齐的问题能解决,数据层面的一致性问题解决不了。
2.3 包里各脚本的分工
脚本包各版本文件名略有出入,按功能对号入座即可,不要死记名字。
| 功能分区 | 典型内容 | 依赖对象 | 什么时候跑 |
|---|---|---|---|
| 环境体检 | 查v$timezone_file、v$timezone_file_supplied、TSTZ 列清单、行数估算 | 数据字典视图 | 窗口前一周 |
| 影响面分析 | 调DBMS_DST.FIND_AFFECTED_TABLES,输出受影响的表和时区列表 | DBMS_DST | 窗口前一周 |
| 试跑段 | BEGIN_PREPARE+UPGRADE_DATABASE,错误写进DST$ERRORS | DBMS_DST | 低峰期,可反复跑 |
| 正式段 | END_PREPARE、BEGIN_UPGRADE、UPGRADE_DATABASE、END_UPGRADE | DBMS_DST | 停机窗口内 |
| 校验段 | 版本核对、快照比对、失效对象统计 | 上一阶段产物 | 正式段结束后立刻 |
| 清理段 | 删快照表、删错误表、删临时基线表 | SYS 权限 | 观察期结束后 |
2.4 这份包管不到的三件事
别指望一个 SQL 脚本包把周边全包了,有三块必须自己收尾。
一是客户端侧。应用服务器上 Oracle Client 的 tzfile、JDBC 驱动自带的时区数据、甚至某些语言的运行时库,都有自己的一份时区规则。数据库升到新版本、客户端还停在旧版本,ORA-01805照样复现。升级前后要逐个节点核对,这一条我吃过亏。
二是主备架构。主库做的升级会通过 redo 传到备库,但备库的二进制里必须已经包含新版本 tzfile,否则 apply 会直接停住。顺序是先把所有节点的二进制和补丁对齐,再在主库动数据,
本文还有配套的精品资源,点击获取