达梦DM8导出SQL文件:从dexp二进制到DDL/INSERT文本脚本

发布时间:2026/11/3 21:27:03
达梦DM8导出SQL文件:从dexp二进制到DDL/INSERT文本脚本

1. 先把“导出SQL文件”这件事的定义对齐

达梦数据库(DM8)导出SQL文件,我见过太多人第一步就走偏:打开命令行敲一遍dexp,拿到一个.dmp就以为任务完成了。结果对方要的是一份能塞进代码仓库、能贴进工单、能在别的环境里start一下就执行的纯文本 SQL,而dexp给出来的是二进制逻辑备份文件,用文本编辑器打开全是乱码。所以这篇东西我想先把这个概念掰开:在 DM8 语境里,“导出SQL文件”至少包含三种诉求,分别是结构脚本(DDL)、数据脚本(INSERT 文本)、以及逻辑备份(dmp 二进制)。三者用途完全不同,工具链也不一样,混着用必然返工。

这篇文章面向的是需要在日常运维、交付、测试环境搭建、数据搬移中真正产出.sql文件的同学。无论你是刚接手一套 DM8 库、以前主要用别的数据库想找对应物,还是写了几年 SQL 现在第一次被要求交一份“库结构+基础数据”的脚本包,这里面的参数取舍、命令拼装、踩坑点都尽量讲透。文章里的库名、模式名、表名一律做了脱敏替换,你按自己的实际对象替换即可;部分参数在不同小版本上可能存在细微差异,我会在涉及处标注“以dexp help实际输出为准”,这是从业者该有的基本习惯——别迷信任何一篇文档,包括这一篇。

先给一个结论性的对照,后面所有章节都是围绕这张表展开的。

诉求典型产出推荐手段是否可读文本
库结构迁移、建表脚本归档ddl.sql图形工具生成DDL、数据字典拼装、disql spool是
少量基础数据/字典表数据data.sql(INSERT语句)数据字典拼装 + disql spool是
整库/整模式备份搬迁xxx.dmpdexp 逻辑导出否,二进制
临时核对、给业务方看几行数据结果集文本disql spool 直接输出查询是

2. 四种导出路线到底怎么选

2.1 dexp 逻辑导出:它不是给你生成 SQL 文本的

dexp是 DM8 自带的逻辑导出工具,和dimp配对使用。它的产物是自有格式的二进制文件,内部按对象封装,导入时必须用dimp解包。它的强项是完整保留对象依赖关系、权限、约束、索引、触发器、存储过程等,并且能通过一致性参数拿到一个相对干净的时间点快照。你要是想拿它当“生成SQL脚本”的工具,方向就错了。

但这不代表dexp在这篇文章里不重要。恰恰相反,绝大多数“我要导出一份 SQL”的真实场景,最后交付物里都应该附一个dmp。原因很实在:你手工拼的 DDL 脚本极容易漏掉索引、约束、注释、序列、同义词这些边角料,而dmp是完整的、可回滚的。我自己的习惯是双份交付:一份dmp兜底,一份.sql给人看、给版本库管。这样既满足“可读”的交付要求,又留了一条无损恢复的后路。

2.2 disql spool:最朴素也最可控的文本出口

disql是 DM8 的命令行交互工具,自带SPOOL命令,作用是把终端输出重定向到文件。这条路子看起来土,但它有一个别人比不了的优势:输出什么内容,完全由你的 SQL 决定。你想导表结构就查数据字典,想导数据就拼 INSERT,想导一份统计报表就直接 SELECT。没有黑盒,没有“工具帮我做了什么”的不确定性。

代价是你得自己写查询、自己处理字段类型、自己控制排版。对于几十张表的小库,这是半小时的活;对于几百张表的中大型库,就得靠脚本和模板批量生成,这部分的实操我会在第 4 章给到可直接改的模板。

2.3 图形化管理工具:适合一次性、少量对象

DM 的管理工具在对象树上对模式、表、视图等节点提供了生成 DDL / 导出脚本的入口,勾选对象、选好输出路径,点确定就能拿到一份格式规整的建表语句,字段、主键、注释一般都带上了。这是新手最快拿到一份“看起来像样”的 DDL 的方式,我个人在应急时也常用。

它的局限也很明显:对象数量一多,勾选就是体力活;不同小版本的菜单位置和导出选项不完全一致,你写的操作手册换台机器就可能对不上;批量、定时、无人值守这些需求它天然不擅长。所以我的定位很清楚——图形工具负责“快速拿到一份能用的”,脚本负责“稳定拿到每次都一样的”。

2.4 迁移/开发工具链:面向跨库和长期维护

还有一类工具是面向数据迁移和日常开发的,比如官方提供的数据迁移工具以及较新的开发者工具,它们通常具备“把对象导出为 SQL 脚本”“把查询结果导出为多种格式”这类能力。跨库搬迁、异构库表结构转换这类活儿,用它们比手写脚本省事得多。

选型上没有绝对优劣,只看你的约束条件。我一般按三个问题决策:对象规模多大?需不需要重复执行?交付物要不要进版本库?三个答案里只要有两个指向“要重复、要可追溯”,那就别犹豫,写脚本。反过来,如果只是临时给同事导三张表的结构,用图形工具点两下,五分钟解决,硬写脚本反而是过度工程。

3. dexp 参数体系:哪些必须懂,哪些保持默认

3.1 连接串与身份选择

DM8 的dexp用USERID传连接信息,格式是用户名/密码@主机:端口。这里有个新手常犯的错:用普通业务账号去做整模式导出,结果导出来的东西缺一半。原因不复杂——导出工具需要读数据字典、需要访问对象定义,权限不足时它不会报错中断,而是跳过或者导出空壳,等你回灌的时候才发现索引没了、约束没了。

我的做法是:结构类导出统一用具备 DBA 权限的账号,业务数据导出才考虑用模式属主账号,减少权限外溢。密码里带特殊字符(比如@、/)时记得处理转义或者改用交互式输入,否则连接串会被截断,报出来的错还特别误导人,看着像“网络不通”,其实是解析错位。

3.2 导出范围控制:把范围钉死,别用“全都要”

范围参数是dexp里最容易出事的一组。FULL=Y是整库,SCHEMAS=指定一个或多个模式,TABLES=指定具体表,EXCLUDE=用来排除某些对象类型。我的经验是:生产环境永远不要图省事用FULL=Y。整库导出动辄几十上百 GB,跑几个小时,中途磁盘满了或者会话被掐断,前功尽弃,而且导出期间对 I/O 的压力是实打实的。

推荐做法是SCHEMAS=精确到模式,再根据需要补EXCLUDE=把大日志表、临时表、历史归档表排掉。多个模式用英文逗号分隔,写完一定回头检查一遍有没有多空格——这个工具对空格不算宽容,参数里混进空格导致的“对象不存在”报错,我至少遇到过三次,每次都要愣一下才反应过来。

QUERY=参数可以给指定表附加过滤条件,做部分数据导出时很有用。但它的引号嵌套在命令行里非常容易出错,我的建议是把整条命令写进 shell 脚本,用变量包起来,别在终端里手敲。手敲一次成功不代表下次还能成功,尤其是条件里带比较符号和中文的时候。

3.3 输出形态:文件、目录、切分、压缩

FILE=指定导出文件名,DIRECTORY=指定输出目录(部分版本支持,以dexp help为准),FILESIZE=控制单个文件大小上限,超过就自动切分成多个文件。这个参数在大库导出时是救命的:单个文件太大,传输、拷贝、校验都难受;切成 2GB 一个,出问题时只需要重传坏的那一个。

COMPRESS=Y会压缩导出文件,体积能小一大截,代价是导出和导入时都要多耗 CPU。判断标准很简单:瓶颈在磁盘和网络就压,瓶颈在 CPU 就不压。LOG=指定日志文件路径,这个参数千万别省,导出过程的每一步、每个对象的处理结果都在里面,出问题排查全靠它。LOG_WRITE=Y可以让日志边跑边写,长任务中途被打断时至少能看到跑到哪一步了。

ROWS=N是个很实用的开关,只导结构不导数据,做“结构基线”时非常合适,速度快、体积小,回灌到测试环境后自己造数据。

3.4 一致性与并行:性能和安全性的拉扯

CONSISTENT=Y用于获取一致性视图,避免导出过程中别的会话提交了新数据导致前后不一致。代价是导出期间需要维护更大的回滚信息,对源库有一定压力。白天业务高峰做导出,一致性开销和时间开销都要算进去,我更倾向于把大导出放到低峰窗口,那时候一致性带来的额外成本相对可接受。

PARALLEL=和TABLE_PARALLEL=控制并行度。这里有个反直觉的点:并行度不是越高越快。我实测过同一套库,并行开到 8 的时候比 4 快不了多少,但源库的 CPU 使用率直接顶到 90% 以上,业务方那边开始有感知了。经验值是先把并行设为 CPU 核数的一半,跑一次看耗时和资源曲线,再决定加还是减。盲目上高并行,最后往往是运维被叫去解释为什么白天系统卡。

4. 一次完整的模式级导出演练

4.1 导出前的环境探查

动手之前先摸清环境,这一步能省掉后面 80% 的返工。字符集、页大小、大小写敏感这几个参数,如果源库和目标库不一致,脚本拿过去就是一堆报错,甚至更糟——不报错但数据错了。DM8 提供了一组函数可以直接查,我整理成下面这张表,建议你存成自己的常用清单。

检查项查询语句为什么要看
版本信息SELECT * FROM V$VERSION;确认小版本,参数支持范围有差异
字符集标识SELECT SF_GET_UNICODE_FLAG();0/1/2 对应不同字符集,跨库迁移必须一致
页大小SELECT SF_GET_PAGE_SIZE();与表空间、大字段存储相关
大小写敏感SELECT SF_GET_CASE_SENSITIVE_FLAG();敏感时未加引号的标识符会被转大写
簇大小SELECT SF_GET_EXTENT_SIZE();影响存储规划,脚本迁移时一般不影响
当前实例SELECT NAME FROM V$INSTANCE;多实例环境防止连错库

字符集这一项我要单独强调。SF_GET_UNICODE_FLAG()返回值不同,意味着库内字符编码策略不同。从一个 UTF-8 环境导出的中文数据脚本,拿到 GB18030 环境去执行,轻则注释变问号,重则插入报错中断。在导出脚本的文件头写一行注释,把源库字符集标识记下来,这是很小的动作,但接手的人会感谢你。

同时确认磁盘余量。逻辑导出的体积大致可以和“数据量 × 压缩比”对应,不压缩的话接近原始数据规模;再加上导出过程本身的临时开销,留出两倍余量比较稳妥。我见过一次因为/home分区只剩 3GB 而告吹的导出,前面跑了四十分钟,最后一步写文件失败,白跑。

4.2 命令拼装与执行

假设要给一个业务模式(下文用APP_SCHEMA代称)做一份带数据的导出,命令大致长这样:

dexp USERID=SYSDBA/******@127.0.0.1:5236 \ SCHEMAS=APP_SCHEMA \ FILE=app_schema_20250101.dmp \ DIRECTORY=/data/export \ LOG=/data/export/app_schema_20250101.log \ LOG_WRITE=Y \ FILESIZE=2048 \ COMPRESS=N \ ROWS=Y \ CONSISTENT=Y \ PARALLEL=4

每一行参数背后都有理由。用SCHEMAS而不是FULL,是为了把导出的影响面锁在一个模式内;FILESIZE=2048意味着单文件 2GB 切分,方便后续传输和校验;COMPRESS=N是因为这套库数据量不大,压不压缩差别有限,而 CPU 是共享资源,能省则省;LOG_WRITE=Y保证日志实时落盘,万一会话被中断,看得见断点在哪。

命令建议写成脚本文件而不是直接敲在终端,一是避免 shell 的历史记录泄露密码(用交互输入或者从环境变量读),二是便于加前后置动作,比如导出前记录时间戳、导出后自动校验文件大小和数量。

4.3 日志与产物解读

跑完之后先在日志里搜几个关键词:successfully、error、warning。DM8 的dexp日志会逐个对象列出处理状态,对象数量和你预期的对不上,就是权限或者范围参数出了问题。我一般会先在源库里跑一条统计:

SELECT COUNT(*) FROM ALL_OBJECTS WHERE OWNER = 'APP_SCHEMA' AND OBJECT_TYPE = 'TABLE';

拿这个数字和日志里的表数量对一下,差得多就说明有问题,别急着往下走。另外注意日志里关于大字段、分区表的提示信息,这类对象经常有额外的处理说明。

产物检查也包括文件层面:文件个数是否等于切分预期、总大小是否和源库数据量在同一量级、有没有出现 0 字节文件。0 字节文件基本可以确定是权限或路径问题,早发现早重跑。

4.4 回灌校验:不看导入结果的导出都是耍流氓

导出的最终目的是能用。我的固定动作是在自己的测试环境回灌一遍,用dimp导入到一个临时模式,然后比对三件事:表数量、关键表行数、索引和约束数量。行数可以在源库和测试库各跑一遍SELECT COUNT(*),多跑几张核心业务表就够,不必全表比对。

dimp USERID=SYSDBA/******@127.0.0.1:5236 \ FILE=app_schema_20250101.dmp \ DIRECTORY=/data/export \ SCHEMAS=APP_SCHEMA \ LOG=/data/import/app_check.log

这一步花不了十分钟,但能挡住“交给下游三天后发现缺表”的尴尬。导出的可信度是靠回灌验证出来的,不是靠日志里那句成功提示。

5. 真正产出 .sql 文本的四条实现路径

5.1 用数据字典拼 DDL:最稳的结构脚本方案

想拿到一份可读的建表语句,最可控的方式是从数据字典里把结构“算”出来,再用disql的SPOOL落盘。DM8 的数据字典与通用 SQL 标准基本一致,ALL_TABLES、ALL_TAB_COLUMNS、ALL_CONSTRAINTS、ALL_IND_COLUMNS、ALL_TAB_COMMENTS、ALL_COL_COMMENTS这些视图都能用。

具体做法是先写一个查询,把某张表的列信息按顺序取出来,人工或脚本拼成CREATE TABLE语句。听起来笨,但对于最终要进版本库的脚本来说,“笨但可复现”远胜“快但不可控”。

SELECT COLUMN_ID, COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE, NULLABLE, DATA_DEFAULT FROM ALL_TAB_COLUMNS WHERE OWNER = 'APP_SCHEMA' AND TABLE_NAME = 'T_ORDER' ORDER BY COLUMN_ID;

拿到这份清单后,DATA_TYPE到 DM8 建表语法的映射要自己过一遍:VARCHAR2带长度,NUMBER要区分是否有精度,DATE、TIMESTAMP、CLOB、BLOB各有写法,DATA_DEFAULT里可能是表达式而不是字面量。不要写个通用映射就想通吃所有类型,我试过,最后补丁比正片还长。更实际的做法是分类型处理:常见类型走通用模板,遇到大字段、自增列、虚拟列就单独拎出来手工确认。

写完 DDL 之后,注释、索引、约束、序列这些“附属品”要单独成段。这些恰恰是手工最容易漏的:表建出来了,字段注释没了;主键有了,唯一索引丢了。建议在脚本里按“建表 → 加注释 → 建索引 → 建约束 → 建序列 → 建视图 → 建存储过程”分块,每块前面加一行注释标题,别人接手时一目了然,也便于分步执行定位错误。

5.2 用 spool 输出查询结果:注意排版参数

disql里执行SPOOL之前,一定要先设置几个会话参数,否则导出来的文本会掺杂列标题、分页符、行数提示,没法直接用。

SET PAGESIZE 0; SET HEADING OFF; SET FEEDBACK OFF; SET LINESIZE 32767; SET LONG 999999; SET TRIMSPOOL ON; SPOOL /data/export/order_rows.sql; SELECT 'INSERT INTO T_ORDER(ID, AMOUNT, CREATE_TIME) VALUES(' || ID || ',' || AMOUNT || ',TO_DATE(''' || TO_CHAR(CREATE_TIME,'YYYY-MM-DD HH24:MI:SS') || ''',''YYYY-MM-DD HH24:MI:SS''));' FROM APP_SCHEMA.T_ORDER; SPOOL OFF;

SET PAGESIZE 0去掉分页,SET HEADING OFF去掉列名,SET FEEDBACK OFF去掉“已选择 N 行”的提示,SET LINESIZE要开大,否则长行会被折行,拼出来的 INSERT 语句直接断成两半,执行就报语法错。SET LONG影响大字段的显示长度,导 CLOB 内容时必须调大,不然会被截断且不报错——这是最阴险的一种,数据静默丢失,你根本发现不了。SET TRIMSPOOL ON去掉行尾空格,配合LINESIZE使用,能让输出文件干净不少。

提示:LINESIZE不是越大越好,超过某些终端的容纳范围可能有意外,实际使用中以 32767 为上限比较常见,如果单行真的超了,说明该换方案——比如把超长字段单独拆出去处理。

5.3 拼 INSERT 语句时那几类必须特殊处理的字段

这是整个导出流程里最容易翻车的地方,我按踩坑频率排个序。

字符串里的单引号。业务数据里出现O'Brien这种名字太正常了,不处理的话拼出来的语句直接语法错误。标准做法是REPLACE(字段, '''', ''''''),把单引号翻倍。这个处理必须加,而且要加在每一处字符串拼接上,漏一个就埋一颗雷。

日期时间类型。直接||拼接会依赖会话的日期格式参数,换台机器就可能格式不符。稳妥写法是TO_CHAR转字符串外层套TO_DATE,把格式显式写死,这样无论目标库参数怎么配都能正确解析。

NULL 值。|| NULL的结果还是 NULL,整行拼出来就残了。要么用NVL换成NULL关键字,要么在拼接外层判空。千万别用空字符串代替,字符串字段里''和NULL在部分上下文里行为不同,能不引入歧义就别引入。

大字段(CLOB/BLOB)。CLOB 还能靠SET LONG硬扛,BLOB 基本没法用文本表示,只能考虑十六进制转换或者干脆用dmp走。遇到 BLOB 就老老实实承认“这个不适合文本导出”,硬来的结果往往是脚本几百 MB 且执行极其缓慢。

自增列。表上有自增列时,直接插入会与自增机制冲突。部分版本提供了临时开关来允许显式插入自增值,用完之后记得关掉,否则后续正常插入会受影响。更省事的办法是导出时把自增列剥离,让目标库自己生成——前提是业务不依赖具体的 ID 值。

5.4 数据量大时的分批与脚本组织

单表几十万行以上,一条SELECT拼全部 INSERT,内存和临时空间都会吃紧,而且生成的单个文件可能几个 GB,编辑器打不开、版本库受不了。这时候要分批:按主键区间或者按时间范围切,每次导出一段,输出到不同文件。

按主键区间的做法比较通用:先查出MIN(ID)和MAX(ID),按固定步长切分成若干区间,每个区间用QUERY或者直接在 SQL 里加WHERE ID BETWEEN ... AND ...,循环执行。步长怎么定?我的经验是单个输出文件控制在 50 到 100MB,既方便传输也方便人工核查。行数不好估算(取决于字段宽度),所以按文件大小反推步长更实际:先跑一批看大小,再调整。

整个导出过程建议包成一个 shell 脚本,开头记录开始时间和环境信息,中间逐表循环,每张表结束打一行“表名 + 行数 + 耗时”,结尾输出汇总。别小看这几行日志,半年后你回头查“当时导的到底是哪一版”,全靠它。

6. 高频问题排查与避坑清单

6.1 字符集、时区与格式参数不一致

这类问题的表现很有迷惑性:脚本执行没报错,但中文全是问号,或者时间整体偏了八小时。前者是字符集不匹配,后者是时区设置不同。排查顺序是:先查源库和目标库的字符集标识是否一致,再查两边的时区设置,最后查日期格式参数。

我的一般处理是在脚本头部加一段环境确认语句,把关键参数打印出来,执行时先看这段输出再往下走。多花十秒,省掉半小时的“数据怎么不对”的困惑。

6.2 大对象、分区表、自增列的特殊处理

分区表在导出时通常整体处理,但如果你手工拼 DDL,分区子句很容易漏。漏了之后表能建出来,但数据全部落在默认分区,查询性能差一个数量级,而且这种问题短期内根本发现不了。

自增列、序列、触发器这几类对象,在文本导出里是最容易被整段遗忘的。我的检查清单是固定的:表、列、主键、唯一约束、普通索引、外键、注释、序列、触发器、视图、存储过程、同义词。每导出一个模式,就对着这张清单勾一遍,缺哪项补哪项。这份清单我用了好几年,每次都能勾出点东西来。

6.3 权限、路径与磁盘的坑

导出路径要有写权限,这一点大家都知道,但有个细节常被忽略:导出工具是以启动它的操作系统用户身份写文件的,不是数据库用户。用 root 启动一次、再用普通用户启动一次,两次的产物属主不一样,后续脚本读取时可能没权限。统一用同一个运维账号执行,省心。

磁盘空间除了算数据量,还要算日志。LOG_WRITE=Y的日志在大对象多的库上可能写到几百 MB,别等到日志把分区撑满才发现。我给自己的规矩是:导出目录单独挂一个分区,阈值告警设在 80%。

6.4 常见报错速查

现象大概率原因处理方向
连接失败,提示网络或服务端口错、实例未启动、连接串被特殊字符截断先用disql手工连一次验证
提示对象不存在模式名大小写不匹配、参数里混入空格检查大小写敏感设置,逐字符核对参数
导出成功但对象数偏少账号权限不足,部分对象被跳过换具备 DBA 权限的账号重跑
脚本执行报语法错长行被折行、单引号未转义调大LINESIZE,检查拼接逻辑
中文变问号字符集不一致比对两边字符集标识,必要时转码
时间偏差时区或日期格式参数不同显式TO_DATE写死格式
回灌后索引缺失手工 DDL 未包含索引对着对象清单补齐

7. 性能调优与长期维护的几条实感

7.1 并行度、批大小与资源曲线的关系

并行相关的参数,我的态度一向是“先保守、看曲线、再放开”。同一套库,并行从 4 提到 8,耗时可能只降了 15%,但源库 CPU 从 60% 冲到 90%,这段时间业务查询的响应时间会明显变差。这笔账要算清楚:你省下的十五分钟,可能是别人多等的十五分钟。

比并行更值得调的是批大小和对象粒度。按表切分、逐表导出,比一次性整模式导出更容易控制,中断后也只需要重跑失败的那几张表,而不是从头再来。这种“可续跑”的设计,比任何参数优化都值钱。

7.2 定时任务与产物留痕

需要定期导出脚本的场景(比如每天给测试环境同步一份结构基线),用操作系统的定时任务调脚本就行。关键是三件事:产物按日期命名、日志按日期留存、失败要有可感知的通知。第三点最容易被忽略,结果某天任务默默失败了一周,等到有人要用的时候才发现最新的一份是七天前的。

我的做法是脚本最后检查退出码和产物文件大小,异常就写一条醒目的标记到一个状态文件里,再由别的监控去读。土办法,但管用。

7.3 导出文件怎么管

dmp文件不适合进版本库,二进制、体积大、无法 diff。.sql脚本倒是适合,但要注意每次重新生成会带来大量无意义的格式差异,比如字段顺序变化、空白字符差异,导致 diff 一片红。解决办法是固定生成顺序(按COLUMN_ID排序)、统一缩进、去掉多余空行,让 diff 只反映真实的结构变更。

至于保留周期,我的习惯是:结构脚本长期保留并纳入版本管理,数据脚本按需生成、用完即弃,dmp备份保留最近若干个版本。全都留着,磁盘迟早撑爆;全都不留,出事时只能干瞪眼。

我个人在实际操作中的体会是,导出这件事最容易出问题的地方从来不是命令本身,而是“以为导完了”和“以为对方要的就是这个”。养成两个小习惯就能避开大半麻烦:一是导出前先问清楚交付物形态,是二进制还是文本、要不要带数据、目标环境什么版本;二是导出后一定回灌验证一遍,别让队友当你的第一个测试用户。至于那些参数细节,用dexp help打出来存一份在本地,比翻任何文档都快。