简介:面向需要进行 Oracle 数据库时区升级与版本校验的 DBA、运维及开发人员,这套 DBMS_DST 时区调整脚本用于将数据库时区版本更新至较新版本,通常配合官方时区补丁使用,适合升级前评估、变更实施与结果核验等场景。压缩包内共 4 个 SQL 文件,整体约 16KB,按检查与执行两类用途组织,并附有脚本说明,便于按先检查、后应用的顺序完成时区版本调整与状态确认。资源已有 1693 人学习,说明其在 Oracle 时区维护场景中具有一定参考价值。使用者可借此梳理 DBMS_DST 的检查项、应用流程与验证思路,减少手工编写脚本和遗漏步骤的风险,也能为补丁配套、时区变更排查及重复环境操作提供可复用的脚本依据。
1. 先别急着解压:Oracle DBMS_DST 时区版本脚本 V1.9 的版本账要先算清
第一次在测试库碰 Oracle DBMS_DST 时区版本脚本 V1.9,我以为是解压后跑一个 SQL 就完事,结果v$timezone_file的版本号没动,应用端还报时区区域无效。后来才明白,调整数据库时区版本脚本 DBMS_DST_scriptsV1.9.zip 不是改一个参数,它围着DBMS_DST包做准备、升级、校验三件事,版本对不上会把停机窗口白白烧掉。这套东西适合正在把老库时区文件往前推的 DBA 和运维,也适合要接时区字段回读的开发。装之前先认清:脚本 V1.9 是操作入口,真正干活的是数据库里的DBMS_DST包和时区文件。
2. 装前环境清单:Oracle 版本、时区文件版本和脚本 V1.9 的匹配
2.1 先查数据库版本与时区文件版本
装任何数据库脚本前,我第一件事不是解压,而是连进目标库把版本账查清。DBMS_DST_scriptsV1.9.zip里的 SQL 和 shell 包装脚本会调用数据库内的DBMS_DST包,如果当前库没有这个包,或者包状态是INVALID,后面解压再整齐也跑不动。另一个容易漏的是时区文件版本:v$timezone_file里的version是当前库正在用的时区文件版本,脚本 V1.9 的目标版本必须比它大,平级或降级都不是这套脚本该干的事。
-- 连进目标库后先跑这一组,确认库身份和时区文件底账 SET PAGESIZE 200 LINESIZE 200 FEEDBACK OFF SELECT * FROM v$version; SELECT version, filename FROM v$timezone_file; SELECT COUNT(*) AS dbms_dst_pkg FROM dba_objects WHERE object_name = 'DBMS_DST' AND object_type = 'PACKAGE'; SELECT name, open_mode, database_role FROM v$database;这段查询的逻辑很直接:v$version看数据库主版本,v$timezone_file记下当前时区文件版本和文件名,dba_objects确认DBMS_DST包存在,v$database确认连的是主库还是备库。参数上没有什么要改的,唯一要注意的是用sysdba或具备查字典权限的账号执行;如果dbms_dst_pkg返回 0,不要继续往下装,先确认数据库补丁和组件状态。备库上也不要直接跑升级,先确认切换和角色。
| 检查项 | 命令 | 合格标准 |
|---|---|---|
| 数据库版本 | SELECT * FROM v$version; | 与脚本 V1.9 说明中的支持范围一致 |
| 当前时区文件 | SELECT version FROM v$timezone_file; | 记下旧版本,目标版本必须更大 |
DBMS_DST包 | 查dba_objects | 状态为VALID |
| 实例角色 | SELECT database_role FROM v$database; | 确认主库/备库,备库先别动 |
| 归档模式 | SELECT log_mode FROM v$database; | 升级前应为ARCHIVELOG |
提示:目标时区版本不要靠猜,从脚本包里的说明文件或变更单取;拿不到目标版本就先别执行
BEGIN_PREPARE。
2.2 操作系统用户、目录和 SQL*Plus 连通性
DBMS_DST_scriptsV1.9.zip最终要落在数据库主机上,用哪个系统用户解压、哪个用户执行 SQL*Plus,会直接影响权限和日志归属。常见做法是用数据库软件属主用户执行,比如oracle,把脚本目录放在独立路径下,不要扔进$ORACLE_HOME里,后面升级补丁时容易被覆盖。目录深度也别太随意,固定成/opt/dbms_dst/scripts_v1.9这种一眼能认出的路径,排错时少翻一层。
# 确认当前系统用户、ORACLE_HOME 和 sqlplus 是否可用 id echo "$ORACLE_HOME" which sqlplus sqlplus -v # 用无密码本地方式连库,确认能进到目标实例 sqlplus -s / as sysdba <<'SQL' SET PAGESIZE 50 SELECT instance_name, host_name FROM v$instance; EXIT SQL这段先看系统身份,再看sqlplus是不是从预期的ORACLE_HOME里出来的。which sqlplus和sqlplus -v能排掉“机器上装了两套客户端、PATH 指到旧目录”的情况。最后的v$instance是确认ORACLE_SID没连错,host_name用来核对是不是目标机器。参数上要把ORACLE_SID、ORACLE_HOME写进当前 shell 或统一的环境文件,别依赖登录时自动带出来的变量。若机器上有多个实例,执行前先echo $ORACLE_SID,再进 SQL*Plus 查v$instance,两步都别省。
2.3 备份与回退底线
DBMS_DST的升级动作会改数据字典里的时区信息,还会扫受影响的表,准备阶段和升级阶段都可能把停机窗口拉长。我的血泪经验是:没有可回退备份,就不要在生产库上碰这套脚本。备份不是“有个昨天的全备”就行,升级前要有一份新的全备加归档日志,并且把当前v$timezone_file的版本号、DBMS_DST相关错误表状态、变更窗口都记进变更记录。回退不是简单执行一个反向包,很多场景要靠备份恢复,所以备份要能覆盖整个升级窗口。
# 升级前做一次全备加归档日志,控制文件自动备份也打开 rman target / <<'RMAN' CONFIGURE CONTROLFILE AUTOBACKUP ON; BACKUP DATABASE PLUS ARCHIVELOG; RMAN这段 RMAN 命令先把控制文件自动备份打开,再做数据库全备和归档日志备份。参数上PLUS ARCHIVELOG会把备份期间产生的归档也收进去,适合升级这种会持续写日志的动作。备份完不要只看“完成”,要确认备份片路径、完成时间、归档日志是否连续。如果库太大,至少要有一次全备加升级前归档,别用“导出几张业务表”代替。升级前把SELECT version FROM v$timezone_file;的结果抄进变更单,后面验证和回退都用得上。
3. 解压与目录落地:DBMS_DST_scriptsV1.9.zip 放在哪、权限怎么给
3.1 上传后先校验压缩包
压缩包传到服务器后,第一眼要看的是包有没有传坏。DBMS_DST_scriptsV1.9.zip这种脚本包,解压坏一个 SQL 文件,后面执行到一半才报错,排查成本比重新传一遍高得多。我一般先算哈希,再列一下压缩包内容,确认顶层目录结构和小节入口,别急着unzip -o覆盖旧目录。旧目录里可能有上一次留下的日志和改过的脚本,直接覆盖会把现场搞乱。
# 先算哈希并列出压缩包内容,确认包完整、顶层目录结构清楚 sha256sum DBMS_DST_scriptsV1.9.zip unzip -l DBMS_DST_scriptsV1.9.zip | sed -n '1,40p'sha256sum用来和传包记录比对,值不一致就重新传,不要带病解压。unzip -l只看列表不解压,重点看有没有多一层同名目录、有没有README、sql、sh这类入口。参数上sed -n '1,40p'只是截前 40 行,避免文件太多刷屏;真正解压前可以先看一眼有没有绝对路径或奇怪的../路径。若包内顶层目录已经叫DBMS_DST_scriptsV1.9,解压目标就指到它的上一级,别再多套一层。
3.2 解压目录与属主权限
脚本目录我习惯固定在/opt/dbms_dst下,按版本分目录,V1.9一个目录,后面换版本不覆盖旧现场。解压后用数据库软件属主用户持有,权限给到属主可读写执行、同组可读执行,其他用户不给写权限。脚本里可能包含 SQL 和 shell,shell 需要执行位,SQL 不需要;但统一给750更省事,至少不会出现“属主能读、执行用户读不了”的翻车。
# 固定目录落地,属主给数据库软件用户,权限按目录统一收口 export DST_HOME=/opt/dbms_dst/scripts_v1.9 mkdir -p "$DST_HOME" unzip -q DBMS_DST_scriptsV1.9.zip -d "$DST_HOME" chown -R oracle:oinstall "$DST_HOME" chmod -R 750 "$DST_HOME" find "$DST_HOME" -maxdepth 2 -type f \( -name '*.sql' -o -name '*.sh' -o -name 'README*' \) -print | sort这段先建固定目录,再解压,然后改属主和权限,最后列出 SQL、shell 和说明文件。DST_HOME是后面执行脚本时要反复用的变量,建议写进当前 shell 的环境文件。oracle:oinstall要换成目标机器上实际的数据库软件用户和组,不要照抄。find里的-maxdepth 2是防止脚本包层级太深刷屏,先看两层内的入口文件,确认 README 指的准备脚本、升级脚本、检查脚本分别在哪个目录。
| 路径/文件 | 建议权限 | 说明 |
|---|---|---|
/opt/dbms_dst/scripts_v1.9 | 750 | 脚本根目录,属主可写 |
*.sh | 750 | 需要执行位 |
*.sql | 640或750 | 由 SQL*Plus 读取,不需要系统执行位 |
logs | 750 | 日志目录,单独建,别和脚本混放 |
README* | 640 | 只读说明,执行前先看 |
注意:不要用
chmod -R 777图省事,脚本目录里可能有连接串和日志,权限放开后后面很难收回。
3.3 环境变量和脚本入口核对
脚本 V1.9 不是独立程序,它依赖sqlplus、ORACLE_HOME、ORACLE_SID和DST_HOME。环境变量写错,最常见的现象是“脚本跑完了,但库没变”,因为连到了另一个实例。我一般把变量集中在当前 shell 里显式export,执行前再 echo 一遍。NLS_LANG也顺手设成AMERICAN_AMERICA.AL32UTF8,避免 SQL*Plus 输出乱码把错误信息遮住。
# 执行前把关键变量显式固定,避免继承登录环境里的旧值 export ORACLE_SID=DEMODB export ORACLE_HOME=/u01/app/oracle/product/dbhome_1 export PATH="$ORACLE_HOME/bin:$PATH" export DST_HOME=/opt/dbms_dst/scripts_v1.9 export NLS_LANG=AMERICAN_AMERICA.AL32UTF8 # 核对变量和 SQL*Plus 版本 env | grep -E '^(ORACLE_SID|ORACLE_HOME|DST_HOME|NLS_LANG)=' sqlplus -v这段把ORACLE_SID、ORACLE_HOME、PATH、DST_HOME、NLS_LANG固定下来。ORACLE_HOME的路径按目标机器实际安装位置改,不要照抄示例。env | grep是为了在执行脚本前做一次眼检,尤其是跳板机上同时有多个实例时。sqlplus -v能确认调用的客户端版本。脚本入口不要凭文件名猜,先看 README 里写的执行顺序;如果 README 缺失,就先用DBMS_DST包的手工命令核对脚本到底做了什么,再决定是否执行包装脚本。
4. 配置与执行:用 DBMS_DST 跑通准备、升级、收尾
4.1 准备阶段:BEGIN_PREPARE 和错误表清理
DBMS_DST的准备阶段不是走形式。BEGIN_PREPARE会扫出受时区文件影响的表,把信息放进sys.dst$affected_tables,同时把转换中可能遇到的问题记到sys.dst$error_table。这一阶段不处理业务数据,但会占用字典和部分表锁,窗口要留够。执行前先把目标时区版本号确认好,下面的&new_tz_version是 SQL*Plus 替换变量,执行时会提示输入。
-- 准备阶段:登记目标时区版本,先看受影响表和错误表 ACCEPT new_tz_version NUMBER PROMPT '输入目标时区版本号: ' EXEC DBMS_DST.BEGIN_PREPARE(&new_tz_version); SELECT COUNT(*) AS affected_cnt FROM sys.dst$affected_tables; SELECT COUNT(*) AS error_cnt FROM sys.dst$error_table;ACCEPT只负责接收一个数字,目标版本从脚本说明取,不要在现场猜。BEGIN_PREPARE执行时间取决于库里的TIMESTAMP WITH TIME ZONE列和表数量,大库可能跑很久,别在中途 Ctrl+C。两个查询一个看受影响表数量,一个看错误数量;error_cnt不是 0 就不要急着END_PREPARE,先把错误表内容拉出来看。
-- 错误表非空时先抽样看,不要直接清空 SET LINESIZE 200 PAGESIZE 100 SELECT * FROM sys.dst$error_table WHERE ROWNUM <= 20;这段只抽样看前 20 行,避免错误太多刷屏。sys.dst$error_table里的报错通常和列类型、触发器、物化视图、无效对象有关。处理方式要看具体报错,常见做法是先修无效对象、禁用相关触发器、处理物化视图刷新,再重新跑准备阶段。准备阶段如果中途失败,不要直接进升级,先把错误表清干净;END_PREPARE是收尾动作,不是跳过错误的开关。
-- 错误表为空后再结束准备阶段 EXEC DBMS_DST.END_PREPARE;END_PREPARE做的是准备阶段收尾,把状态推进到可升级。执行完再查一次v$timezone_file,此时版本通常还没变,别在这里误判失败。准备阶段的日志要SPOOL下来,后面升级出问题,这份日志就是黑匣子。
4.2 升级阶段:BEGIN_UPGRADE、STARTUP UPGRADE 和 END_UPGRADE
升级阶段是整套脚本里最需要停机窗口的一段。BEGIN_UPGRADE登记升级动作,然后要把库重启到UPGRADE模式,再执行END_UPGRADE完成时区文件推进,最后回到正常模式。不同主版本对重启要求略有差异,但单实例的常见顺序就是这样;RAC 要逐节点处理,别在一个节点上跑完就以为全好了。
-- 升级阶段:登记、重启到 UPGRADE 模式、完成升级、回正常模式 EXEC DBMS_DST.BEGIN_UPGRADE(&new_tz_version); SHUTDOWN IMMEDIATE; STARTUP UPGRADE; EXEC DBMS_DST.END_UPGRADE; SHUTDOWN IMMEDIATE; STARTUP;这段要在同一个 SQL*Plus 会话里用sysdba执行,STARTUP UPGRADE不是普通启动,执行完END_UPGRADE后还要再重启回正常模式。BEGIN_UPGRADE之后不要跳过去直接查业务表,升级动作还没完成。SHUTDOWN IMMEDIATE前确认没有长事务和未提交会话,窗口内别让应用重连。RAC 环境要把其他节点停掉或按节点顺序执行,具体看脚本 V1.9 的 README 和数据库补丁说明,不要拿单实例顺序硬套。
升级完成后,v$timezone_file的版本应该变成目标版本。若没变,先看END_UPGRADE是否报错、STARTUP UPGRADE是否真的进到了升级模式、DBMS_DST包是否有效。升级阶段失败不要反复BEGIN_UPGRADE,先查错误表和告警日志,确认状态再决定重跑还是恢复。
4.3 脚本 V1.9 的包装执行与日志留存
脚本 V1.9 的价值是把准备、升级、检查拆成可重复执行的入口,但包装脚本不能替代你对DBMS_DST步骤的理解。执行前先打开SPOOL,把 SQL*Plus 的ECHO、TIMING打开,日志按阶段分文件。常见做法是准备阶段一个日志、升级阶段一个日志、验证阶段一个日志,出问题时按时间线对。数据库自带的utltz_upg_check.sql可以先跑一遍做交叉检查,再按 V1.9 的 README 执行包装入口;如果两边都带清理逻辑,不要重复跑。
# 日志目录单独建,准备阶段先跑数据库自带检查脚本做交叉核对 mkdir -p /opt/dbms_dst/logs sqlplus -s / as sysdba <<'SQL' SET ECHO ON SET TIMING ON SPOOL /opt/dbms_dst/logs/dst_check.log @?/rdbms/admin/utltz_upg_check.sql SPOOL OFF SQL这段先建日志目录,再用 SQL*Plus 执行数据库自带的时区升级检查脚本。@?里的?表示ORACLE_HOME,SPOOL把输出落到固定文件。SET ECHO ON会把执行的每条语句回显,SET TIMING ON记录耗时,后面判断哪一步慢有依据。若 V1.9 包内也有检查入口,先看 README 说明它和自带脚本的关系,不要两套检查同时改字典。包装脚本执行前把DST_HOME指到实际包含 SQL 的目录,日志路径固定,别用带空格的目录名。
5. 避坑与排查:DBMS_DST 时区升级最常见的五个翻车点
5.1 把 DBTIMEZONE 当成时区版本
现象:有人改完ALTER DATABASE SET TIME_ZONE或ALTER SESSION SET TIME_ZONE,查DBTIMEZONE变了,就以为时区版本升级完成,结果v$timezone_file还是旧版本,业务端跨时区字段照样报错。
原因:DBTIMEZONE是数据库默认时区设置,v$timezone_file的version才是时区文件版本。DBMS_DST升级的是时区文件里的规则数据,两者不是一回事。只改DBTIMEZONE不会更新时区规则,也不会处理已经存了时区偏移的历史数据。
解决:先查清楚再动手。
-- 这三个值不是一回事,别拿 DBTIMEZONE 当升级结果 SELECT DBTIMEZONE FROM dual; SELECT SESSIONTIMEZONE FROM dual; SELECT version, filename FROM v$timezone_file;DBTIMEZONE看数据库默认时区,SESSIONTIMEZONE看当前会话时区,v$timezone_file看时区文件版本。升级前后都以v$timezone_file为准,变更单里也记这个版本号。若业务只需要改会话显示时区,那是另一件事,不要混进DBMS_DST升级窗口。
5.2 准备阶段错误表没清空就往下走
现象:BEGIN_PREPARE跑完,sys.dst$error_table里有记录,但执行人看到“准备完成”就去跑END_PREPARE和BEGIN_UPGRADE,升级阶段报错或升级后部分表时区转换不正确。
原因:错误表里的记录通常代表某些表、列、触发器或物化视图无法自动处理。准备阶段的作用就是把这些问题提前暴露,错误表非空时直接推进,等于把问题带到升级阶段。升级阶段再回头处理,窗口更紧,回退成本更高。
解决:准备阶段结束后必须查错误表,非空就逐条看。
-- 错误表非空时先抽样,按对象类型和报错逐条处理 SELECT * FROM sys.dst$error_table WHERE ROWNUM <= 20;常见处理是先修无效对象、处理相关触发器、确认物化视图状态,再重跑准备阶段。不要用TRUNCATE清错误表来“让它通过”,那只是把黑匣子盖上。错误表清零后再END_PREPARE,再进升级。准备阶段的日志和错误表输出一起留档,后面复盘用得上。
5.3 连错实例或权限不足
现象:脚本执行完没报错,但目标库的v$timezone_file没变;或者执行到一半报ORA-01031: insufficient privileges、ORA-00942: table or view does not exist。
原因:跳板机上多个实例共存,ORACLE_SID或TNS_ADMIN指到了测试库;或者执行用户不是sysdba,访问不了sys.dst$*字典表。权限不足时,包装脚本可能前半段成功、后半段失败,看起来像“脚本有 bug”。
解决:执行前显式固定实例,执行中用sysdba。
# 执行前先确认变量,再连库确认实例名 echo "$ORACLE_SID" sqlplus -s / as sysdba <<'SQL' SELECT instance_name, host_name FROM v$instance; SELECT USER FROM dual; SQLinstance_name和host_name要和变更单一致,USER应该是SYS。若用 TNS 连接,先把TNS_ADMIN和连接串固定,不要依赖默认tnsnames.ora。权限上,DBMS_DST的执行和sys.dst$*查询都需要较高权限,普通业务账号跑不了。包装脚本里如果写死了连接串,执行前打开看一眼,别让脚本连到旧库。
5.4 客户端时区文件没跟着换
现象:数据库端v$timezone_file已经到目标版本,SQL*Plus 里查询正常,但应用端报“时区区域无效”或时间偏移不对,尤其是 Java 应用和旧版客户端。
原因:数据库时区文件升级了,客户端侧的时区数据还是旧的。JDBC 驱动、JVM 的tzdata、Oracle 客户端都可能带自己的时区规则。数据库端升级只解决服务端,客户端不跟着换,跨时区转换仍可能出错。
解决:数据库升级完成后,把客户端和驱动版本纳入检查。Java 应用看 JDK 的tzdata版本,Oracle 客户端看安装目录下的时区文件,连接池重启后再做业务回读。不要只让应用“重连一下”就算完,旧连接池可能还持有旧会话。应用端时区问题要和数据库升级分开记录,避免把客户端问题误判成数据库没升级。
5.5 没留回退窗口
现象:升级到一半发现错误表处理不了,或者升级后业务回读不对,想回退时发现没有可用备份,或者备份不包含升级前的归档日志。
原因:把DBMS_DST当成“可随时反向执行”的普通脚本。实际升级会改字典数据,回退往往要靠备份恢复,反向包不是万能后悔药。窗口预留不足时,执行人容易在压力下跳过备份和验证。
解决:升级前做全备加归档,记录旧版本号,准备回退步骤。回退步骤要写清楚:停应用、停库、从备份恢复、启动到升级前状态、验证v$timezone_file回到旧版本。备份验证不要只看 RMAN 输出“完成”,要确认备份片可读。升级窗口至少留出准备、升级、验证和回退四段,别把验证时间挤没。
6. 装完怎么验证:从 v$timezone_file 到业务时间字段的回读
脚本跑完不等于装完。我最后一定做三层验证:第一层看字典版本,第二层看错误表,第三层做业务时间字段回读。先查v$timezone_file的version是否等于目标版本,再确认sys.dst$error_table没有残留错误,最后建一张临时表写一条带时区的时间,按 UTC 回读一次。临时表用完就删,别留在业务 schema 里。
-- 第一层和第二层:版本、错误表、包状态一起看 SELECT version, filename FROM v$timezone_file; SELECT COUNT(*) AS error_cnt FROM sys.dst$error_table; SELECT object_name, status FROM dba_objects WHERE object_name = 'DBMS_DST' AND object_type = 'PACKAGE'; -- 第三层:临时表回读,确认时区字段还能正常转换 CREATE TABLE dst_check_t ( id NUMBER, ts TIMESTAMP WITH TIME ZONE ); INSERT INTO dst_check_t VALUES (1, FROM_TZ(TIMESTAMP '2024-01-01 00:00:00', 'UTC')); SELECT id, TO_CHAR(ts AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS ts_utc FROM dst_check_t; DROP TABLE dst_check_t;v$timezone_file的版本要对上目标值,error_cnt应为 0,DBMS_DST包状态应为VALID。临时表这段只验证TIMESTAMP WITH TIME ZONE的写入和回读,不碰业务表;FROM_TZ用UTC构造时间,AT TIME ZONE 'UTC'回读,避免引入具体地区时区名。若业务表里有TIMESTAMP WITH TIME ZONE字段,再抽一张小表做同样回读,别直接扫大表。验证完把临时表删掉,日志和查询结果一起归档。
| 验证层 | 看什么 | 通过标准 |
|---|---|---|
| 字典版本 | v$timezone_file.version | 等于目标版本 |
| 错误表 | sys.dst$error_table | 无残留错误 |
| 包状态 | dba_objects里的DBMS_DST | VALID |
| 业务回读 | 临时表/抽样表的时间字段 | 写入和回读一致 |
从那以后我每次装这套脚本,先在测试库把v$timezone_file旧版本、备份完成时间、回退 SQL 三样钉在变更单上,再碰生产;升级完不看到业务字段回读通过,不把“完成”两个字写进记录。希望帮到你。
本文还有配套的精品资源,点击获取