1. 三个命令两种格式,先把选型这件事定下来
前两天有个做数据迁移的同行在群里甩了张截图,八千万行的订单表,用 IMPORT 跑了快五个小时还没结束,最后抛了个事务日志满的错误。我看了一眼他的语句,把 OF DEL 换成 OF IXF、IMPORT 换成 LOAD、再加上 NONRECOVERABLE,同样的数据量二十来分钟就落地了。这不是什么玄学,纯粹是命令语义和文件格式没匹配上业务场景。
DB2 里做批量数据搬运,绕不开三个命令和两种格式这三个词:IMPORT 负责带事务日志的逐行插入,EXPORT 负责把查询结果落成文件,LOAD 负责绕过大部分日志的高速灌库;文件格式上,IXF 是带完整表结构和二进制数据的外部格式,DEL 是带定界符的纯文本格式。搞不清楚这五者的边界,就会出现"该快的慢、该稳的崩"。
这篇内容适合三类人看:一是刚接手 DB2 批量作业、被 SQL31xx 系列报错折磨过的开发;二是需要在系统间做数据交换、纠结选 IXF 还是 DEL 的运维;三是从其他数据库转过来、习惯用一条 INSERT 打天下的工程师。下面所有示例都基于 DB2 LUW,表名和库名一律做了脱敏,参数值给的是可复现的经验区间,实际执行时按自己的环境调整。
2. 吃透 IXF 和 DEL 的文件结构
选格式之前得先知道文件里到底装了什么。很多人把 IXF 当成"DB2 专用的二进制乱码",把 DEL 当成"就是 CSV",这两种认知都不太对,直接导致后面导入导出各种对不上。
2.1 IXF 的五类记录:结构信息才是它的价值所在
IXF 全称 Integrated Exchange Format,虽然名字里有"格式"两个字,但它其实是一套分记录类型组织的外部文件协议。一个完整的 IXF 文件由五种记录拼起来,每种记录有自己的标识字节:
| 记录类型 | 标识 | 作用 |
|---|---|---|
| 头记录 | H | 记录 IXF 版本号、生成时间、字符集编码 |
| 表记录 | T | 目标表名、列数、数据约定、行长约束 |
| 列记录 | C | 每列一条,包含列名、类型、长度、精度、小数位、是否可空、在行内的偏移 |
| 数据记录 | D | 真正的行数据,按二进制或压缩十进制存储 |
| 应用记录 | A | 由导出方的应用程序自定义的附加信息,DB2 自身一般不用 |
关键在于T 记录和 C 记录。这两类记录把表结构完整地写进了文件里,所以IMPORT ... OF IXF ... REPLACE_CREATE才能做到"目标表不存在就自动建表"。这是 DEL 完全做不到的事情——DEL 文件里只有数据,没有任何元信息,目标表必须先存在,列的类型、顺序、数量都得靠人去对。
另一个常被忽略的点是H 记录里带编码信息。IXF 在导出时会把源库的代码页写进去,导入到不同代码页的库时,DB2 会自动做转换。这就是为什么跨平台、跨代码页迁移时,IXF 通常比 DEL 更省心——DEL 的编码问题得靠CODEPAGE参数手动指定,猜错了就是一堆乱码或者 SQL3120W 这种字符转换告警。
2.2 DEL 的定界规则:看似简单,坑全在转义
DEL 是 Delimited ASCII 的缩写,本质就是定界文本。默认规则是这样的:字段之间用逗号分隔,字符型字段用双引号包裹,NULL 值写成NULL三个大写字母,日期写成YYYY-MM-DD,时间戳写成YYYY-MM-DD-HH.MM.SS.NNNNNN。
听上去挺简单,但真正让 DEL 变得麻烦的是字符内容本身可能包含分隔符。比如某个备注字段里写了付款,已确认,导出时 DB2 会把它包成"付款,已确认",没问题。但如果备注里还包含一个双引号,就得写成两个双引号""来转义。再如果备注里有换行符,这个换行会原样保留在引号内部——这时候你用wc -l去数行数就会发现行数对不上,因为一条逻辑记录可能跨了多个物理行。
我在实际项目里踩过最典型的一次:导出 DEL 后用脚本按行切分做数据校验,结果校验出来的行数比源表少了两千多行,排查了半天才发现是几个长文本字段里带了\r\n。所以DEL 文件永远不要用行数去估算数据量,要么用SELECT COUNT(*),要么在导出时就加STRIPTNUL之类的修饰符把尾部空白处理掉,再配合可靠的解析库而不是手写切分。
还有一点:DEL 的空值表示是可以改的,通过MODIFIED BY NULSTRING指定成别的字符串,比如\N或者NULL之外的任意标记。这在和其他系统交换数据时很有用,因为不少工具默认把空串和 NULL 混为一谈,需要一个明确的哨兵值来区分。
2.3 数据类型到文本的映射陷阱
DEL 是文本,所有类型都要序列化成字符串,这一步天然有损。最容易出问题的是三类:
第一类是浮点和 DECIMAL。DB2 的 DOUBLE 类型在 DEL 里默认输出成带小数点的科学计数或定点形式,精度可能被截断。DECIMAL 还好,但如果目标端的 DECIMAL 精度不够,导入时会直接报 SQL3125W 或者干脆截断。要控制小数点的字符,得用MODIFIED BY DECPT指定,比如欧洲区域习惯用逗号当小数点,就得写DECPT','。
第二类是时间戳。DB2 默认的 DEL 时间戳格式是YYYY-MM-DD-HH.MM.SS.NNNNNN,注意日期和时间之间是短横线不是空格,这和很多人脑子里想的YYYY-MM-DD HH:MM:SS不一样。导入时格式对不上,就会报 SQL3149N「从文件中读到的行数不是有效行」。修正办法是显式指定:MODIFIED BY TIMESTAMPFORMAT="YYYY-MM-DD HH:MM:SS.UUUUUU",注意这里的 U 表示微秒位。
第三类是字符型的尾部空格。CHAR 类型在 DB2 里是定长的,导出时尾部空格会原样写进 DEL 文件的引号内。导入到 VARCHAR 列时这些空格会保留下来,导致后续关联查询对不上。MODIFIED BY STRIPTNUL就是用来在导入时去掉字符字段两端空白(其实是去掉 DEL 记录里的填充符)的,实测下来这一条能省掉大量"为什么两边数据一样却 JOIN 不上"的排查时间。
IXF 就没这些问题,因为它是二进制存储,DECIMAL 按压缩十进制原样保存,时间戳按内部格式保存,跨平台读写不丢精度。代价是文件不能直接被人读,也不能直接喂给 Excel 或者别的数据库。
3. EXPORT 实操:把数据干净地搬出来
EXPORT 是三个命令里最"安全"的一个,因为它只读不写,不涉及事务日志,也不会锁表。但它有几个参数必须搞清楚,否则导出来的文件在导入端根本用不了。
3.1 一条完整导出语句的拆解
先看一条我认为比较规范的 IXF 导出:
EXPORT TO /data/stg/orders_202401.ixf OF IXF MESSAGES /data/stg/export_orders.msg SELECT * FROM app.orders WHERE create_time >= '2024-01-01' AND create_time < '2024-02-01';这条语句里每一段都有明确分工。TO后面是文件路径,注意这个路径是数据库服务器侧的路径,不是客户端路径——如果你通过命令行处理器连的是远程库,文件会落在服务器上,本地是看不到的。这一点新手很容易搞错,总以为文件在自己电脑上。
OF IXF指定格式,如果换成OF DEL,同一条 SELECT 也能跑,只是输出变成文本。MESSAGES指定消息文件,导出过程的行数统计、告警信息都写在这里,出问题时第一个要看的就是它。
SELECT 部分可以写任意合法的查询,包括 JOIN、聚合、子查询。这一点很重要——EXPORT 支持在导出时做数据裁剪和字段变换,不需要先建临时表。比如只导部分列、把两个表 JOIN 后导出、甚至用 CASE WHEN 做值映射,都可以。
再看 DEL 版的关键差异:
EXPORT TO /data/stg/orders_202401.del OF DEL MODIFIED BY CHARDEL'"' COLDEL'|' CODEPAGE=1208 MESSAGES /data/stg/export_orders.msg SELECT * FROM app.orders WHERE create_time >= '2024-01-01' AND create_time < '2024-02-01';这里把字符定界符改成了双引号(默认就是),列分隔符改成了竖线。为什么要改成竖线?因为业务数据里的地址、备注字段极大概率含逗号,用竖线能大幅降低转义概率。不过要提醒一句,改成竖线之后如果字段内容里本身有竖线,照样得转义,没有一劳永逸的方案,只能根据数据特征选冲突概率最低的那个字符。
3.2 MODIFIED BY 常用修饰符组合
MODIFIED BY后面能跟的修饰符不少,我整理了几个高频的,实际使用时按需组合:
| 修饰符 | 作用 | 典型场景 |
|---|---|---|
COLDEL x | 指定列分隔符 | 数据含逗号时换成竖线或制表符 |
CHARDEL x | 指定字符定界符 | 数据含双引号时换成单引号 |
CODEPAGE=n | 指定代码页 | 1208 是 UTF-8,1386 是 GBK |
DECPT x | 指定小数点字符 | 跨区域数据交换 |
TIMESTAMPFORMAT="..." | 指定时间戳格式 | 对接非 DB2 系统 |
DATEFORMAT="..." | 指定日期格式 | 同上 |
NULSTRING x | 指定空值标记 | 对接把空串当 NULL 的系统 |
STRIPTNUL | 导入时去字符填充 | CHAR 转 VARCHAR 场景 |
LOBSINFILE | LOB 内容单独存文件 | 大字段导出 |
IMPLIEDDECIMAL | 隐式补小数点 | 定点数文本无小数点时 |
有个细节值得说:CHARDEL'"'这种写法在不同 shell 下的转义行为不一样。如果你在 bash 里直接敲,双引号可能被 shell 吃掉,导致 DB2 收到的参数是空字符串,然后报语法错误。稳妥的写法是把整个命令放进单引号包裹的脚本里,或者用CHARDEL"\""这种显式转义。我个人的习惯是把导出语句写成 .sql 文件,用db2 -tvf执行,彻底避开 shell 层转义,这个习惯救过我很多次。
3.3 大表与 LOB 的导出策略
表大到一定程度,导出本身也会变成瓶颈。几个实操层面的做法:
分批导出。按时间或主键区间切成多个文件,每个文件控制在几 GB 以内。好处有两个:一是单次失败不用整体重来,二是后面 LOAD 时可以并行加载多个文件到同一个表(LOAD 支持多次执行加载到同一目标表,只要模式选对)。
LOB 字段单独处理。表里如果有 CLOB、BLOB、DBCLOB,默认会把内容内联进主文件,导致文件体积暴涨、单行长度超限。加LOBSINFILE后,LOB 内容会被拆到独立的文件里,主文件只保留引用。这个参数在导出大文本表时几乎是必加的,不加的话很容易撞上 SQL3150N「输入文件中数据格式不正确」或者干脆导出中断。
导出前评估文件大小。可以用一条简单的查询估算:
SELECT SUM(LENGTH(col1) + LENGTH(col2) + ...) / 1024 / 1024 AS est_mb FROM app.orders WHERE create_time >= '2024-01-01';这个估算不精确,但能帮你判断目标磁盘够不够。我见过有人在剩余空间只有 2G 的目录上导 8G 的表,跑到一半磁盘满,SQL1239N 直接中断,前面几小时的活白干。
3.4 一次实际导出记录
上个月做的一次跨环境迁移,源表约 4200 万行,含两个 CLOB 字段,平均每行 1.8KB 左右。我的操作顺序是:
先用 IXF 导全量结构加数据,加LOBSINFILE,观察到文件拆成了主文件 8.2GB 加 LOB 目录若干。耗时约 19 分钟。然后对比了一下 DEL 方案,同样是 4200 万行,DEL 文件 11.7GB,耗时 26 分钟。IXF 体积小、速度快、还能带结构,只要目标端也是 DB2,没有理由选 DEL。
例外情况是目标端不是 DB2,比如要喂给数据分析平台或者消息队列,那就只能选 DEL,因为对方读不了 IXF 的二进制。这个取舍点必须提前确认清楚,不要等导完了才发现对方解析不了。
4. IMPORT 实操:小批量精准落库
IMPORT 的定位很明确:数据量不大、需要事务保护、要求失败可回滚的场景。它把插入操作包在事务里,中途出错可以整体回滚,这是 LOAD 给不了的。
4.1 五种导入模式的适用边界
IMPORT 的模式比很多人想象的多,一共五种:
| 模式 | 行为 | 适用场景 |
|---|---|---|
INSERT | 纯追加,主键冲突直接报错 | 目标表空表或确定无重复 |
INSERT_UPDATE | 存在则更新,不存在则插入 | 增量同步,需要主键或唯一索引 |
REPLACE | 先删全表数据再插入 | 全量覆盖,表结构已存在 |
REPLACE_CREATE | 先删表再按 IXF 结构重建 | 表结构可能变化,必须 IXF 格式 |
CREATE | 按 IXF 结构建新表后插入 | 目标表不存在且不能删 |
INSERT_UPDATE是最容易被误用的一个。它的更新逻辑是基于主键或唯一索引整行覆盖,不是字段级合并。也就是说如果源数据里某个字段是 NULL,更新后目标行对应字段也会变成 NULL,而不是保留原值。想保留原值只能自己在导出端用 COALESCE 处理,或者改用 MERGE 语句。
REPLACE_CREATE值得单独说。它要求文件必须是 IXF 格式,因为要读取 C 记录来建表。用 DEL 文件执行这个模式会直接报错。它的价值在于把表结构也纳入了迁移范围,避免了"源表加了列、目标表没跟上"这种经典事故。
4.2 COMMITCOUNT 的计算过程
IMPORT 最常见的失败原因就是事务日志满(SQL0964C)。原因是它默认每导入完整个文件才提交一次,大文件直接把活动日志撑爆。
解决办法是加COMMITCOUNT n,每 n 行提交一次。但 n 取多少不能拍脑袋,得算。计算逻辑是这样的:
先看库的日志配置:
db2 get db cfg for SAMPLE | grep -i log关注三个值:Size of log files(LOGFILESIZ,单位 4KB 页)、Number of primary log files(LOGPRIMARY)、Number of secondary log files(LOGSECOND)。
假设LOGFILESIZ = 2048页,LOGPRIMARY = 10,LOGSECOND = 20,那么日志总容量是:
2048 页 × 4KB = 8MB/文件 8MB × (10 + 20) = 240MB但绝不能把这 240MB 全用掉,因为同一个库上还有别的应用在跑,必须留余量。按经验最多占用 60% 到 70%,取 160MB。
然后估算单行产生的日志量。一条 INSERT 的日志大致包括:行数据本身、每个索引的变更记录、事务控制开销。经验公式是:
单行日志 ≈ 行字节数 × (1 + 索引个数 × 1.2) + 200 字节假设行宽 800 字节,表上有 3 个索引:
800 × (1 + 3 × 1.2) + 200 = 800 × 4.6 + 200 = 3880 字节那么 COMMITCOUNT 大约是:
160MB / 3880 字节 ≈ 43000 行取个保守整数,用 30000 到 40000 之间都合理。如果你导出的是窄表、索引少,这个值可以放大到十几万;如果是宽表带大字段,可能要降到几千。
提示:这个公式是经验估算,真实日志开销还受行迁移、索引分裂、锁升级影响。第一次用某个 COMMITCOUNT 值运行时,务必监控日志使用率,用
db2pd -db SAMPLE -logs或者快照看Log space available。
4.3 消息文件与错误定位
IMPORT 一旦出错,信息全在MESSAGES指定的文件里。这个文件的长相大概是:
SQL3109N The utility is beginning to load data from file ... SQL3110N The utility has completed processing. "50000" rows read. SQL3149N "37" rows were rejected. SQL3186W Rows were rejected because of...关键看两个数:rejected 行数和拒绝原因。最常见的是 SQL3116W(字段值被截断)和 SQL3125W(字符转换失败)。前者说明目标列长度不够,后者一般是编码没对上。
有个技巧:导入时加MESSAGES ... MSGLEVEL=ALL可以把每一行的错误都打出来,便于逐条排查。但数据量大时这个文件会很大,只在排查阶段用。
5. LOAD 实操:大批量高性能加载
LOAD 是 DB2 批量灌数的性能王牌,代价是它基本不写事务日志,一旦失败不能简单回滚,而且加载后表会进入特殊状态。
5.1 LOAD 快在哪里
要理解 LOAD 为什么快,得看它和 IMPORT 的机制差异:
IMPORT 走的是逐行 INSERT 路径,每一行都要生成日志记录、维护索引、检查约束、更新统计信息,全程在事务保护下,等于把 OLTP 的写入路径重复了几千万次。
LOAD 走的是批量写入路径,它把数据直接格式化后写入数据页,索引默认在最后统一重建(INDEXING MODE REBUILD),日志只记录极少量信息。同时 LOAD 可以开多个 CPU 并行处理不同数据块,磁盘也可以并行写多个容器。
实测下来的量级差异通常是这样:相同数据量下,LOAD 的耗时大约是 IMPORT 的 1/3 到 1/10,差距随行宽、索引数量、约束复杂度放大。索引越多,REBUILD 模式的优势越明显,因为 IMPORT 要逐行维护 B 树,而 LOAD 是一次性排序构建。
5.2 关键参数逐项拆解
一条我用得比较顺的 LOAD 语句:
LOAD FROM /data/stg/orders_202401.del OF DEL MODIFIED BY COLDEL'|' CODEPAGE=1208 REPLACE INTO app.orders_stg NONRECOVERABLE STATISTICS YES WITHOUT INDEXES INDEXING MODE REBUILD CPU_PARALLELISM 4 DISK_PARALLELISM 4 SAVECOUNT 200000 TEMPFILES PATH /data/tmp/loadtmp MESSAGES /data/tmp/load_orders.msg;逐个说:
REPLACE INTO表示先清空目标表再加载。LOAD 的可用模式有 INSERT、REPLACE、RESTART、TERMINATE 四种,常用的是前两个。注意 LOAD 不支持INSERT_UPDATE,这个能力只有 IMPORT 有。
NONRECOVERABLE表示不生成可恢复的副本,加载后如果数据库需要前滚恢复,这张表的数据无法恢复。它换来的是性能提升和日志量下降。生产库上要谨慎,如果库开启了归档日志且需要恢复到任意时间点,这个参数就不能用,得改成COPY YES(加载前做一次备份)。这个取舍必须跟 DBA 确认,不能自己拍。
STATISTICS YES WITHOUT INDEXES表示加载后收集表和列统计信息,但不收集索引统计。为什么不含索引?因为索引刚 REBUILD 完,统计信息重建成本高,很多时候用不上。如果后续查询依赖索引统计做执行计划,可以改成STATISTICS USE PROFILE复用之前生成的统计文件。
INDEXING MODE REBUILD是性能关键。另外两个选项是INCREMENTAL(边加载边维护索引,慢)和AUTOSELECT(让优化器选,通常偏 REBUILD)。只要表数据量占比变化大,一律选 REBUILD。
CPU_PARALLELISM和DISK_PARALLELISM控制并行度。CPU 并行度建议不超过可用逻辑核数的一半,留出余量给其他负载。磁盘并行度一般和表空间容器数量对齐,比如表空间有 8 个容器,填 4 到 8 都合理。两个值不是越大越好,超过了实际资源只会互相争抢。
SAVECOUNT是检查点间隔,每 N 行记录一次恢复点。LOAD 中断后可以用RESTART从最近检查点继续。这个值太小会增加额外开销,太大则中断后要重跑很多。一般设成总量的 1/10 到 1/20。
TEMPFILES PATH指定临时文件目录。LOAD 过程会产生大量临时文件用于排序和索引重建,空间需求大约是数据文件大小加上索引总大小。这个目录最好是独立磁盘,且预留 2 倍以上空间。空间不足会报 SQL3501W 或者直接中断。
5.3 SET INTEGRITY 与表状态处理
LOAD 结束后第一件事不是庆祝,是检查表状态。执行:
SELECT TABNAME, STATUS FROM SYSCAT.TABLES WHERE TABSCHEMA = 'APP' AND STATUS <> 'N';如果返回了C(check pending)或者X(in progress),说明表还不能正常访问。原因是 LOAD 为了性能,把检查约束、生成列、物化查询表这些校验推迟了。必须显式执行:
SET INTEGRITY FOR app.orders_stg ALL IMMEDIATE CHECKED;这句会让 DB2 立刻校验所有约束,违规数据会被移到异常表(如果配了FOR EXCEPTION IN ...),否则整句失败。想让校验把坏数据挑出来而不是报错,标准写法是:
SET INTEGRITY FOR app.orders_stg ALL IMMEDIATE CHECKED FOR EXCEPTION IN app.orders_stg USE app.orders_stg_e;前提是异常表已经按目标表结构建好。这个步骤我在第一次做的时候完全不知道,LOAD 完直接去查表,报了一堆状态相关的错误,查了半天文档才反应过来。
还有一种情况是业务上允许暂时不做校验、先把数据灌进去再说,可以用ALL IMMEDIATE UNCHECKED。它跳过校验直接解除 pending 状态,但坏数据会留在表里,后续查询可能出错。这条我不建议在生产用,除非你百分之百确定数据干净。
5.4 中断后的 TERMINATE 与 RESTART
LOAD 中途因为网络、磁盘、锁等原因中断,表会停在 loading 状态,这时候直接重跑会报错。正确的处理顺序是:
先执行LOAD FROM ... TERMINATE INTO ...把上一次的加载清掉,释放临时状态,然后重新执行完整的 LOAD。如果不想从头再来,可以用RESTART从最近检查点继续,前提是数据文件没变、表结构没变。
判断该用哪个的逻辑:如果中断原因只是资源问题、数据文件完整,用 RESTART 省时间;如果数据文件本身有问题需要重新导出,那就必须先 TERMINATE 再重来,否则会在旧的临时状态上叠加新数据。
5.5 一次性能对比记录
同样的 4200 万行数据,同一份 IXF 文件,同一套硬件,我记录了三种方案的耗时:
| 方案 | 耗时 | 日志占用 | 可回滚 |
|---|---|---|---|
| IMPORT + COMMITCOUNT 30000 | 4 小时 12 分 | 高,需持续监控 | 是 |
| IMPORT 单次提交 | 未完成,日志满中断 | 极高 | 是 |
| LOAD + REBUILD + 并行度 4 | 21 分钟 | 极低 | 否 |
这个差距不是理论值,是实实在在跑出来的。代价也很明确:LOAD 方案在中断后不可回滚、加载后需要 SET INTEGRITY、不满足任意时间点恢复要求。所以生产环境的大批量灌数,通常是"先 LOAD 到临时库或临时表、校验通过后再用 INSERT ... SELECT 进正式表",既拿到速度,又保住正式表的可恢复性。
6. 常见问题速查与避坑
这一节是前面内容的补充,都是我实际踩过或者帮别人排查过的。
6.1 报错速查表
| 错误码 | 含义 | 常见原因 | 处理方向 |
|---|---|---|---|
| SQL3107W | 消息文件中有更多信息 | 正常提示 | 打开 msg 文件看细节 |
| SQL3110N | 处理完成,显示读取行数 | 正常 | 核对行数与预期 |
| SQL3149N | 无效行数 | 格式与声明不符 | 查 DATEFORMAT、TIMESTAMPFORMAT |
| SQL3116W | 字段值被截断 | 目标列长度不够 | 扩列或改类型 |
| SQL3125W | 字符转换失败 | 编码不匹配 | 加 CODEPAGE |
| SQL0964C | 事务日志满 | IMPORT 未分批提交 | 加 COMMITCOUNT 或改 LOAD |
| SQL3501W | 表空间不足 | 临时文件空间不够 | 换 TEMPFILES PATH |
| SQL3550W | 字段数不匹配 | 文件列数与表列数不一致 | 用 METHOD P 或补空列 |
SQL3550W 值得展开。它出现在文件里的字段数量与目标表列数不一致的时候。解决办法有两个:一是导出时用 SELECT 显式列出需要的列,保证数量和顺序完全对齐;二是导入时用METHOD P (1, 2, 3)指定取文件里的哪几列以及映射顺序。后者在对接别人给的文件时特别有用,因为对方的列顺序你改不了。
6.2 编码、分隔符、空值三大坑
编码。统一用 UTF-8(代码页 1208)是最省心的方案,源库、目标库、导出文件、导入参数四处保持一致。如果库里存的是 GBK(1386),导出时不加 CODEPAGE 会按库的默认代码页写,导入到 UTF-8 库时就得显式指定CODEPAGE=1386让 DB2 知道源文件是什么编码,它再做转换。搞反了方向,转换结果就是乱码加一堆 SQL3125W。
分隔符。选分隔符的原则是"数据里出现概率最低的字符"。常见选择:竖线、制表符、\x01这种控制字符。逗号是最差的选择,因为业务文本里到处都是。选好之后导出和导入两侧必须完全一致,少一个参数就全盘错位。
空值。DB2 的 DEL 默认用NULL表示空值,但有些导出工具会把空串导成空串、把 NULL 导成空,两者在文本上无法区分。对策是在导出端就用NULSTRING指定一个绝不会出现在业务数据里的标记,比如\N或者<NULL>。同时导入端用同样的NULSTRING参数,保证语义对齐。
6.3 日志、锁与表空间
IMPORT 的使用要注意:
- 大表必须加
COMMITCOUNT,前面已经算过。 - 导入期间表上有排他锁,业务查询会被阻塞。尽量在低峰期跑,或者导入到临时表再切换。
INSERT_UPDATE模式在有大量更新的场景下,会因为锁竞争和日志膨胀变得很慢,不如用 LOAD 到临时表再 MERGE。
LOAD 的使用要注意:
TEMPFILES PATH所在磁盘要有足够空间,估算方式是数据文件大小加索引大小再乘 1.5。- 加载期间表处于加载状态,其他会话访问会报错或者挂起。
NONRECOVERABLE和归档日志模式冲突,要提前确认 DBA 的意见。
EXPORT 的使用要注意:
- 导出路径是服务器侧,别在客户端找文件。
- 大表导出会占用较多排序空间,
db2 get db cfg里的SORTHEAP如果太小,会大量落盘。 - 导出不锁表,但会读一致性的快照,如果导出时间长,可能读到较早的数据版本。
6.4 几条踩出来的经验
先在万行级数据上跑通全链路。不要拿生产全量数据做第一次尝试,先用SELECT ... FETCH FIRST 10000 ROWS ONLY导一个小文件,导入一个小表,验证格式、编码、约束、统计信息都正常,再上全量。
导出语句和导入语句放在同一个文件里成对维护。格式参数、列顺序、编码这些必须在两侧完全对称,分开维护迟早会漂移。我习惯在脚本里写一个变量定义分隔符和编码,导出导入都引用它。
LOAD 之后一定要立刻查表状态和行数。行数对不上但状态正常的情况我遇到过,原因是数据文件里混进了空行,LOAD 跳过了但没报错。加一条SELECT COUNT(*)核对是最低成本的保险。
跨版本迁移先查兼容性。IXF 的版本在新旧版本之间一般兼容,但某些新类型(比如新版本的 XML 存储格式、变长字符集)在旧版本上读取可能出问题。稳妥做法是导出后用旧版本的 LOAD 先试一个小文件。
保留消息文件至少一个月。SQL31xx 系列的信息在出事后是唯一的线索,磁盘紧张也别删。消息文件里通常包含每一行被拒绝的原因,事后复盘全靠它。
我个人在实际操作中的体会是,这三个命令和两种格式的组合,本质上是在速度、安全、可移植性三者之间做选择,没有哪个方案全面最优。IXF 加 LOAD 换来速度,代价是放弃回滚能力;DEL 加 IMPORT 换来可读性和兼容性,代价是性能;IXF 加 IMPORT 是折中,适合中等数据量又需要事务保护的场景。真正让我少踩坑的不是记住这些参数,而是每次动手前先问自己一句:这批数据出错之后,我要的是什么?是要能回滚,还是要能尽快重跑?答案不同,选型就不同。