如何修改Oracle数据库时区_DBMS_DST包与时区文件升级


Oracle 12c+ 升级时区文件前必须先执行 DBMS_DST.BEGIN_PREPARE 声明准备,否则 UPGRADE_TIMEZONE 将报 ORA-30078 错误;该步骤不锁表但要求目标版本合法且无未处理时区敏感表,失败后仅能在 BEGIN_PREPARE 至 END_PREPARE 间软回退。

Oracle 12c+ 升级时区文件前必须调用 DBMS_DST.BEGIN_PREPARE

不执行这一步就直接 upgrade_timezone,会报错 ora-30078: time zone file not in upgrade mode。这是 oracle 强制的流程校验——它要求你先“声明准备升级”,系统才会把时区文件切换到可写状态。

常见错误是跳过准备阶段,直接跑升级脚本;或者在 BEGIN_PREPARE 后没验证就继续,结果发现目标版本不支持当前数据库版本(比如 DB 是 19c,却想升到 tzfile v45,而官方只允许升到 v44)。

  • DBMS_DST.BEGIN_PREPARE 只接受一个参数:version_number,必须是 SELECT * FROM V$TIMEZONE_FILE 中列出的合法版本号
  • 执行后查 DBA_TSTZ_TABLES,确认没有未处理的时区敏感表(否则后续 END_PREPARE 会失败)
  • 准备阶段不锁表、不阻塞 DML,但不能同时启动另一个准备任务

升级失败后如何回退到原时区文件版本

Oracle 不提供自动回滚 UPGRADE_TIMEZONE 的命令。一旦执行成功,V$TIMEZONE_FILE.VERSION 就永久变更,回退只能靠还原备份或重建数据库——所以升级前必须有完整 RMAN 备份,且备份里包含旧版 timezlrg.dat 文件。

真正能“软回退”的窗口只在 BEGIN_PREPAREEND_PREPARE 之间:此时还没改数据字典,执行 DBMS_DST.END_PREPARE 就能退出准备态,相当于什么都没发生。

  • 如果已执行 UPGRADE_TIMEZONE 但尚未重启实例,可以尝试 ALTER SYSTEM SET TIME_ZONE = 'OLD_VALUE' SCOPE=SPFILE,但这只影响新会话的 SYSTIMESTAMP 显示,不改变已存储的 TIMESTAMP WITH TIME ZONE
  • 检查 DBA_TSTZ_TAB_COLUMNS 中所有 TIMESTAMP WITH TIME ZONE 列是否已完成调整(STATUS = 'SUCCESS'),失败项会导致部分数据时区偏移异常
  • 升级后首次重启时,Oracle 会自动运行时区调整作业(DBMS_DST.FIND_AFFECTED_TABLES 触发),这个过程可能持续数小时,期间不能停库

为什么 DBMS_DST.FIND_AFFECTED_TABLES 返回空结果但升级仍报错

这个函数只扫描当前用户下含 TIMESTAMP WITH TIME ZONE 的表,但 Oracle 内部组件(如审计表 AUD$、调度日志 SCHEDULER$_JOB_RUN_DETAILS)也可能含时区列——它们不归普通用户管,但升级时照样要处理。

典型现象是:自己建的表全 OK,FIND_AFFECTED_TABLES 返回空,可 UPGRADE_TIMEZONE 还是卡住并报 ORA-30088: datetime/interval precision is out of range

  • 必须用 SELECT OWNER, TABLE_NAME FROM DBA_TAB_COLUMNS WHERE DATA_TYPE = 'TIMESTAMP WITH TIME ZONE' 全库扫描,别只信 FIND_AFFECTED_TABLES
  • 某些 Oracle 组件表(如 WRI$_ADV_SQLT_PLANS)在升级过程中被隐式访问,若其所在表空间 offline 或段损坏,也会中断升级流程
  • 升级前建议临时禁用审计(AUDIT_TRAIL=NONE)和调度器(EXEC DBMS_SCHEDULER.DISABLE('SYS.SCHEDULER$_EVENT_QUEUE')),减少干扰

19c 数据库升级时区文件后 SYSTIMESTAMP 显示不对

不是时区文件问题,而是 SYSTIMESTAMP 永远返回 OS 系统时间 + 主机时区偏移,和数据库时区(DBTIMEZONE)无关。很多人误以为升级后 SYSTIMESTAMP 应该跟着 DBTIMEZONE 变,其实不会。

真正受时区文件升级影响的是:TIMESTAMP WITH TIME ZONE 字段的解析逻辑、FROM_TZ 函数对缩写(如 PST/PDT)的识别、以及跨 DST 边界的时间计算精度。

  • 查当前数据库时区用 SELECT DBTIMEZONE FROM DUAL,它默认是 ‘+00:00’,除非你显式执行过 ALTER DATABASE SET TIME ZONE
  • 若需让应用看到统一时区时间,应使用 CAST(SYSTIMESTAMP AS TIMESTAMP WITH TIME ZONE) AT TIME ZONE 'Asia/Shanghai',而不是依赖 DBTIMEZONE
  • 升级后第一次查询 V$TIMEZONE_NAMES 可能变慢,因为 Oracle 要重新加载新版时区规则树,属正常现象

事情说清了就结束。最麻烦的从来不是命令怎么敲,而是升级前漏掉某个隐藏的时区列,或者以为备份了数据就等于备份了时区上下文。