当前位置: 首页 > ORACLE > 正文

我们的文章会在微信公众号IT民工的龙马人生博客网站 ( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢!
由于博客中有大量代码,通过页面浏览效果更佳。

摘要

上周在某客户现场迁移Oracle数据到YashanDB中,在元数据迁移时,采用 DBMS_METADATA.GET_DDL的方式,但是此方案某部分版本中,这条官方的方法会遇到 ORA-00600(小概率事件)—— DDL 出不来,后续的所有的工作都会卡住。本文从 为什么必须绕开 GET_DDL手工脚本的设计要点LONG 型默认值与分区边界怎么安全读取怎么用与边界在哪 几方面,介绍一份在字典视图上 拼装 CREATE TABLE 的 PL/SQL 脚本(get_table_ddl.sql),作为 DBA 工具箱里的兜底方案。


1)为什么需要「手工 DDL」:GET_DDL 不是永远可靠

生产里常见几类痛点:

  • 不可靠性:个别环境下对复杂对象调用 GET_DDL 会触发内部错误(如 ORA-00600),使得原来以来GET_DDL的功能都无法使用。
  • 可控性:黑盒包出错时,你很难在会话里快速「拆开」看卡在哪一步;而基于 DBA_* 视图的拼装,每一步都可观测、可注释、可裁剪。

因此:GET_DDL 能用时优先用;不能用或不敢用时,需要一条可复制的字典拼装路径。


2)脚本在做什么:设计理念与数据流

get_table_ddl.sql 的定位是:在 SQL*Plus 中运行,向 DBMS_OUTPUT 打印一张表的 CREATE TABLE 文本

整体数据流可以概括为:

DBA_TABLES / DBA_TAB_COLUMNS / DBA_PART_* …
        → 内存中拼接 CLOB(列、默认值、NULL、表空间、分区定义)
        → print_clob 分块输出(避免超长单行被截断)

GET_DDL 的差异在于:不依赖 DBMS_METADATA 内部转换链路,只读字典、自己组字符串,从而避开部分内核路径上的缺陷。


3)核心实现要点

3.1 列定义:类型、NULL、表空间与临时表

  • 类型字符串:对 CHAR/NCHAR/VARCHAR2/NVARCHAR2NUMBER(含精度标度)、FLOATRAW 等做显式拼接;带 DATA_TYPE_OWNER 的对象类型则输出 "OWNER"."TYPE" 形式。
  • NOT NULL:直接根据 DBA_TAB_COLUMNS.NULLABLE 追加 NOT NULL
  • 普通表:从 DBA_TABLESTABLESPACE_NAME,在闭合括号后追加 TABLESPACE "..."
  • 全局临时表(GTT):根据 TEMPORARY='Y' 生成 CREATE GLOBAL TEMPORARY TABLE,并按 DURATION 区分 ON COMMIT DELETE ROWSON COMMIT PRESERVE ROWS

3.2 默认值:必须正视 DATA_DEFAULT 仍是 LONG

DBA_TAB_COLUMNS.DATA_DEFAULT 在不少版本里仍是 LONG。若用普通 SELECT ... INTO VARCHAR2 容易长度不够或行为怪异。脚本里用 DBMS_SQL + DEFINE_COLUMN_LONG / COLUMN_VALUE_LONG 分片读取,拼成 CLOB,再压成单行(去掉换行)接到 DEFAULT 后——这是这类脚本里最容易被忽略、却最容易翻车的一块。

3.3 分区表:键列、RANGE/LIST 的边界值、子分区

  • 分区类型与子分区类型来自 DBA_PART_TABLES分区键列来自 DBA_PART_KEY_COLUMNS子分区键列来自 DBA_SUBPART_KEY_COLUMNS(子分区类型为 NONE 时按无子分区处理)。
  • HIGH_VALUE 同样可能是 LONG:对分区、子分区分别用独立函数,按与默认值相同的方式读出,再做空白规范化(norm_ws),拼进
    VALUES LESS THAN (...)(RANGE)或 VALUES (...)(LIST)。
  • 各分区/子分区的 TABLESPACE 若存在,会一并输出。

3.4 输出大文本

最终 DDL 放在 CLOB 里,通过 DBMS_LOB.SUBSTR 按 32K 分块 PUT_LINE,避免超长 DDL 在客户端被截断。

脚本开头通过 SET 关闭了分页、控制 SERVEROUTPUTLINESIZE,都是为了 「一次跑完、完整可复制」


4)使用方法(最小步骤)

  1. 打开脚本,修改包体中的 属主与表名(脚本内 g_ownerg_table_name)。
  2. 使用具备读取 DBA_* 视图权限的账号(如 DBA 或授权角色)在 SQL*Plus / sqlcl 中执行。
  3. DBMS_OUTPUT 中的结果复制为 .sql 文件即可(注意目标环境字符集、标识符大小写与引用)。

示例(逻辑上等同于:把变量改成你的 OWNERTABLE_NAME 后执行脚本):

sqlplus / as sysdba @get_table_ddl.sql

5)适用场景与已知边界(务必心里有数)

适合:

  • GET_DDL 报错或不稳定时,快速拿到「主结构」DDL
  • RANGE/LIST(含子分区)场景下,需要 带边界值 的建表语句做核对或离线存档。
  • 此时遇到的BUG为10G的,所以本脚本此时只考虑到10G版本,其它版本建议继续采用官方的GET_DDL的方法。

不适合或需二次加工:

  • 索引、约束、触发器、授权 等不在本脚本输出范围内(完整对象迁移仍建议配合 DBMS_METADATA 其它对象类型或专用导出工具)。
  • HASH 等分区类型 的 DDL 与 RANGE/LIST 的拼法不同,当前脚本对边界值主要按 RANGE/LIST 语义拼接;遇到 HASH/INTERVAL 等需按实际语法扩展或改用官方工具。
  • 虚拟列、Identity、复杂默认值、加密列、LOB 存储子句 等若未在字典拼装逻辑中覆盖,生成结果可能与原始 DDL 有差异——应视为 「结构草稿」,上线前必须比对、测试。

6)与 GET_DDL 的关系:互补而非替代

维度 DBMS_METADATA.GET_DDL 本手工脚本
完整度 官方包可覆盖多类对象与子对象 聚焦 单表壳子 + 常见分区
稳定性 多数环境足够好 600 场景 下可作为兜底
可维护性 黑盒 开源在自己手里,可按库内规范改

实践建议:平时仍以 GET_DDL / Data Pump / 厂商工具为主;一旦现场出现 ORA-00600 且阻塞交付,把本文脚本类方案放进 Runbook,能显著减少「无 DDL 可用」的真空时间。


6)脚本获取

关注公众号,后台回复“脚本”二字,已经关注并回复过的就直接群里面下载即可。
脚本都是开源的,未收取任何费用

结语

DBA 的工作里,「能用的官方 API」永远优先;但 「官方 API 失灵时的第二条路」 往往决定你在故障或变更窗口里是否从容。get_table_ddl.sql 的价值不在于取代元数据引擎,而在于:把表结构从字典里拆成可观测的步骤,在 GET_DDL 不可信时仍能交出一份可执行的 CREATE TABLE。把重复、易错的拼装交给脚本,把判断留给现场。

当 `DBMS_METADATA.GET_DDL` 撞上 ORA-00600:一份可落地的「手工拼表 DDL」脚本:等您坐沙发呢!

发表评论

gravatar

? razz sad evil ! smile oops grin eek shock ??? cool lol mad twisted roll wink idea arrow neutral cry mrgreen

快捷键:Ctrl+Enter