我们的文章会在微信公众号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/NVARCHAR2、NUMBER(含精度标度)、FLOAT、RAW等做显式拼接;带DATA_TYPE_OWNER的对象类型则输出"OWNER"."TYPE"形式。 - NOT NULL:直接根据
DBA_TAB_COLUMNS.NULLABLE追加NOT NULL。 - 普通表:从
DBA_TABLES取TABLESPACE_NAME,在闭合括号后追加TABLESPACE "..."。 - 全局临时表(GTT):根据
TEMPORARY='Y'生成CREATE GLOBAL TEMPORARY TABLE,并按DURATION区分ON COMMIT DELETE ROWS与ON 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 关闭了分页、控制 SERVEROUTPUT 与 LINESIZE,都是为了 「一次跑完、完整可复制」。
4)使用方法(最小步骤)
- 打开脚本,修改包体中的 属主与表名(脚本内
g_owner、g_table_name)。 - 使用具备读取
DBA_*视图权限的账号(如 DBA 或授权角色)在 SQL*Plus / sqlcl 中执行。 - 将
DBMS_OUTPUT中的结果复制为.sql文件即可(注意目标环境字符集、标识符大小写与引用)。
示例(逻辑上等同于:把变量改成你的 OWNER、TABLE_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」脚本:等您坐沙发呢!