DM8 切库实战:这不是类型替换,是语义迁移
适用场景 :DM8 迁移至 MySQL 8.x、KingbaseES V8/V9、神通数据库(OSCAR)
核心结论 :迁移失败的根源,往往不是表建不出来,而是语义悄悄变了AI味道非常浓郁的文章,你猜不到我用了几个主流的AI才搞出这个文档
目录
- 为什么切库比想象中难
- 三条路线的难度画像
- [速查表:4 张表覆盖核心映射决策](#速查表:4 张表覆盖核心映射决策)
- 数值类型映射
- 字符串 / LOB / 二进制映射
- 日期时间与默认值
- 对象模型与大字段处理
- 落地原则:不是看到类型就映射
- 五个最值得警惕的坑
- 一张真实业务表的三路迁移全过程
- 一张总表,一句总结
一、为什么切库比想象中难
很多人第一次做数据库迁移,会把它理解成一件"体力活":把表结构导出来,在目标库执行;把数据导出来,再写进去。听起来像流水线,做起来才知道坑在哪。
真正的问题不在于表能不能建出来 ,也不在于数据能不能导进去,而在于:
原来在 DM8 里默认成立的那套语义,在目标库里并不一定成立。
字段名没变,类型名看起来也差不多,脚本甚至能顺利执行------但业务层的含义已经悄悄变了。这种"执行通过但结果错了"的问题,是迁移项目最难排查的一类故障。
所以,数据库迁移从来不是一次类型替换,而是一场语义迁移。
二、三条路线的难度画像
把 DM8 到三个目标库的路线放在一起看,差异非常清楚。
DM8 → MySQL:不是兼容迁移,更像结构重设计
这是三条路线里改造量最大的一条,问题不只是语法不同,而是很多基础假设都不一样:
- DM8 的
DATE含时分秒,MySQL 的DATE只有日期 - DM8 的
NUMBER灵活可变,MySQL 往往要压成整数或定点数 - InnoDB 有单行大小限制和索引长度限制,大量在 DM8 里"定义自然"的结构,迁过来不得不降配
结论:DM8 → MySQL 更像一次结构重设计,而不是轻量迁移。
DM8 → KingbaseES:最像迁移的一条路线
三个目标库里"最顺手"的选择。KingbaseES 和 DM8 的距离明显更小,数值类型、序列、触发器这类对象保留可能性更高。
但这不等于"无脑迁"。DATE 语义、默认值函数、触发器兼容模式这类问题依然存在。这条路线的特点是改造可控,而不是零改造。
结论:KingbaseES 路线更像一次严肃但可控的工程迁移。
DM8 → 神通(OSCAR):同语系,不等于零摩擦
神通与 Oracle 风格接近,DM8 也偏 Oracle 语系,直觉上会觉得迁移很顺------这个判断只对了一半。
越是"看起来相近"的目标库,越容易在细节上踩坑。字符/字节语义差异、自增列约束配套、大字段写入策略、触发器细节写法,这些问题在初期不显山露水,一旦遇到真实数据就会非常具体。
结论:DM8 → 神通是"同语系迁移",不是"零摩擦迁移"。
三、速查表:4 张表覆盖核心映射决策
1. 数值类型映射速查表
| DM8 类型 | MySQL 常见落点 | KingbaseES 常见落点 | 神通常见落点 | 备注 |
|---|---|---|---|---|
TINYINT |
TINYINT |
SMALLINT |
TINYINT |
Kingbase 常上提一档 |
SMALLINT |
SMALLINT |
SMALLINT |
NUMBER(5) |
神通偏 Oracle 数值表达 |
INTEGER / INT |
INT |
INT / INTEGER |
NUMBER(10) |
神通通常不保留 INT 名称 |
BIGINT |
BIGINT |
BIGINT |
NUMBER(19) |
神通按精度重写 |
BIT |
TINYINT(1) |
SMALLINT |
BOOLEAN |
三库语义最不统一 |
BOOLEAN / BOOL |
TINYINT(1) |
BOOLEAN |
BOOLEAN |
MySQL 本质仍是数值表达 |
NUMBER(p,0) p≤2 |
TINYINT |
NUMERIC(p,0) |
NUMBER(p,0) |
MySQL 会压缩类型 |
NUMBER(p,0) p=3...4 |
SMALLINT |
NUMERIC(p,0) |
NUMBER(p,0) |
同上 |
NUMBER(p,0) p=5...6 |
MEDIUMINT |
NUMERIC(p,0) |
NUMBER(p,0) |
同上 |
NUMBER(p,0) p=7...9 |
INT |
NUMERIC(p,0) |
NUMBER(p,0) |
同上 |
NUMBER(p,0) p=10...18 |
BIGINT |
NUMERIC(p,0) |
NUMBER(p,0) |
同上 |
NUMBER(p,0) p>18 |
DECIMAL(p,0) |
NUMERIC(p,0) |
NUMBER(p,0) |
MySQL 回退 decimal |
NUMBER(p,s) s>0 |
DECIMAL(p,s) |
NUMERIC(p,s) |
NUMBER(p,s) |
三边差异较小 |
NUMBER 无精度 |
常补成 DECIMAL(38,10) |
NUMERIC |
NUMBER |
⚠️ 必须人工确认 |
DECIMAL(p,s) / NUMERIC(p,s) |
DECIMAL(p,s) |
NUMERIC(p,s) |
DECIMAL(p,s) / NUMBER(p,s) |
一般可平移 |
DOUBLE / FLOAT |
DOUBLE |
DOUBLE PRECISION |
DOUBLE PRECISION |
名称不同,语义接近 |
REAL |
FLOAT |
REAL |
REAL |
MySQL 类型名不同 |
2. 字符串 / LOB / 二进制映射速查表
| DM8 类型 | MySQL 常见落点 | KingbaseES 常见落点 | 神通常见落点 | 备注 |
|---|---|---|---|---|
VARCHAR(n) n≤16383 |
VARCHAR(n) |
VARCHAR(n) |
VARCHAR(n) 或按语义换算 |
神通要关注字符/字节语义 |
VARCHAR(n) n>16383 |
LONGTEXT |
TEXT |
CLOB |
长字段三边差异大 |
VARCHAR 无长度 |
LONGTEXT / TEXT |
TEXT |
CLOB |
⚠️ 必须人工确认 |
NVARCHAR(n) |
VARCHAR(n) |
VARCHAR(n) |
按字符语义放大后转 VARCHAR / CLOB |
神通常需长度换算 |
CHAR(n) n≤255 |
CHAR(n) |
CHAR(n) |
CHAR(n) 或按语义换算 |
神通仍要看字节/字符语义 |
CHAR(n) n>255 |
VARCHAR(n) |
CHAR(n) |
CHAR(n) 或转 CLOB |
MySQL 与其他两库处理不同 |
NCHAR(n) |
CHAR(n) / VARCHAR(n) |
CHAR(n) |
按字符语义放大后转 CHAR / CLOB |
神通侧要防止超限 |
CLOB / NCLOB |
LONGTEXT |
TEXT |
CLOB |
不宜只看类型名 |
TEXT / LONG |
LONGTEXT |
TEXT |
CLOB |
MySQL 和神通差异明显 |
BLOB |
LONGBLOB |
BYTEA |
BLOB |
二进制字面量写法完全不同 |
RAW(n) |
VARBINARY(n) |
BYTEA |
VARBINARY(n) |
Kingbase 更喜欢 BYTEA |
BINARY(n) |
VARBINARY(n) |
BYTEA |
VARBINARY(n) |
MySQL/神通保留长度概念 |
VARBINARY(n) |
VARBINARY(n) |
BYTEA |
VARBINARY(n) |
同上 |
神通侧特别提示:
- 源字段是字符语义时,目标侧不能只照抄长度
- 多字节字符场景下,长度很容易在目标库被放大
- 一旦超过目标库限制,普通字符串列会直接退化成
CLOB
3. 日期时间与默认值速查表
3.1 日期时间类型
| DM8 类型 | MySQL 常见落点 | KingbaseES 常见落点 | 神通常见落点 | 风险提示 |
|---|---|---|---|---|
DATE |
DATETIME |
TIMESTAMP(0) |
DATE |
⚠️ 最容易丢时间语义 |
TIMESTAMP |
DATETIME |
TIMESTAMP |
TIMESTAMP |
三边较稳定 |
TIMESTAMP(n) |
DATETIME(n) |
TIMESTAMP(n) |
TIMESTAMP(n) |
MySQL/神通通常只保留到 6 位 |
DATETIME(n) |
DATETIME(n) |
TIMESTAMP(n) |
TIMESTAMP(n) |
目标类型名可能不同 |
TIME |
TIME 或保持原样 |
多数可直通 | 多数可直通 | 结合应用层使用方式确认 |
3.2 默认值函数
| DM8 默认值 | MySQL 常见处理 | KingbaseES 常见处理 | 神通常见处理 | 风险提示 |
|---|---|---|---|---|
SYSDATE / SYSDATE() |
CURRENT_TIMESTAMP |
CURRENT_TIMESTAMP |
直通或兼容改写 | 目标函数名不一定一致 |
SYSTIMESTAMP |
CURRENT_TIMESTAMP |
CURRENT_TIMESTAMP |
CURRENT_TIMESTAMP |
典型兼容改写点 |
CURRENT_TIMESTAMP() |
改成不带括号形式 | 多数可接受 | 改成不带括号形式 | 有些库不接受空括号 |
NOW() / GETDATE() |
改成 CURRENT_TIMESTAMP |
需确认是否支持 | 需确认是否支持 | 不建议无脑照搬 |
| 文本 / BLOB / JSON 字段默认值 | 通常不建议保留 | 看目标库能力 | 看目标库能力 | MySQL 尤其敏感 |
4. 对象模型与大字段处理速查表
4.1 自增、序列、触发器
| 维度 | MySQL | KingbaseES | 神通 | 风险提示 |
|---|---|---|---|---|
| 自增模型 | AUTO_INCREMENT |
GENERATED BY DEFAULT AS IDENTITY |
AUTO_INCREMENT |
不是同一套对象模型 |
| 是否适合保留序列主路径 | 否 | 是 | 是 | MySQL 通常要改思路 |
原有 SEQ.NEXTVAL 习惯 |
需要重构 | 多数可保留或兼容改写 | 多数可保留或兼容改写 | 业务主键链路要重点验证 |
| Oracle 风格触发器 | 不适合直接照搬 | 兼容性较好 | 兼容性较好但细节要改 | 不能因为"支持触发器"就默认兼容 |
OLD/NEW / WHEN 等细节 |
需重写概率高 | 中等 | 中等到高 | 要做逐条校对 |
4.2 BLOB / CLOB 与二进制字面量
| 目标库 | BLOB 常见落点 | CLOB 常见落点 | 二进制字面量写法 | 风险提示 |
|---|---|---|---|---|
| MySQL | LONGBLOB |
LONGTEXT |
X'HEX' |
大字段多时容易触发行/索引限制联动 |
| KingbaseES | BYTEA |
TEXT |
'\\xhex'::BYTEA |
读写较自然,但仍要测大样本 |
| 神通 | BLOB |
CLOB |
TO_BLOB(HEXTORAW('HEX')) |
大 BLOB 往往不能内联,需分块处理 |
四、落地原则:不是看到类型就映射
速查表是"查什么",这一节是"怎么决策"。真实项目里,很多决定需要先判断字段的业务角色,再选落点。
原则 1:NUMBER 先分三类,再决定怎么落
- 整数型字段 (主键、状态码、计数器):精度边界明确,MySQL 里可以压到
TINYINT/INT/BIGINT;KingbaseES 和神通更适合保留NUMERIC(p,0)/NUMBER(p,0) - 金额类字段 (金额、比例、单价、税额):不要因为它也是
NUMBER就压整数,应直接保留DECIMAL(p,s)/NUMERIC(p,s)/NUMBER(p,s) - 历史偷懒定义的泛数值字段 (直接写
NUMBER或精度极宽):最不适合自动映射,必须人工确认业务含义再决定落点
原则 2:DATE 不看名字,看代码里怎么用它
如果应用层把 DM8 的 DATE 当成时间戳使用(映射 LocalDateTime、用于排序/审计/增量同步),迁到 MySQL 应落 DATETIME,迁到 KingbaseES 应落 TIMESTAMP,而不是继续落 DATE。
只有能明确证明该字段本来就只有年月日语义,才应该迁成真正的 date-only 类型。
否则最典型的故障不是 DDL 报错,而是"时间静悄悄丢了"。
原则 3:字符串字段要同时看三个维度
VARCHAR2(2000 CHAR) 这类字段迁到 MySQL,要同时评估:
- 按目标字符集折算后的实际字节成本
- 它是否参与主键、唯一键、普通索引
- 它是否会与其他大字段叠加后触发行大小限制
三个条件有两个偏高,就不能只做"类型替换",而应该进一步决定是缩短长度、改索引设计,还是退化为 TEXT / CLOB。
原则 4:默认值要"对齐行为",不是"替换函数名"
迁移时真正应该保留的是默认值的行为语义,不是源 SQL 文本。比如:
SYSDATE/SYSTIMESTAMP的核心是"插入时取当前时间",不必拘泥于函数名CURRENT_TIMESTAMP()带括号的写法,在部分目标库中要改为不带括号- 文本列、大字段列、JSON 列的默认值能力在不同数据库差异很大,MySQL 尤其要单独核查
原则 5:序列、触发器、自增列要作为整体处理
这三类对象通常是一套联动机制,不能拆开单独迁移。
- 若源库是"序列 + before insert 触发器 +
:NEW.ID赋值"这一套,迁到 MySQL 大概率需要整体切成AUTO_INCREMENT或应用层发号 - 迁到 KingbaseES / 神通,可以选择继续保留 sequence 路线,但触发器语法、默认值函数、对象依赖关系都要同步校对
判断标准不是"目标库支不支持触发器",而是"这条主键生成链路能不能在目标库里稳定重建"。
五、五个最值得警惕的坑
坑 1:同叫 DATE,语义却完全不同
| 数据库 | DATE 语义 |
|---|---|
| DM8 | 含时分秒(类似 Oracle) |
| MySQL | 只有日期,时间部分截断 |
| KingbaseES | 只有日期 |
| 神通 | 更接近 Oracle 语义,通常保留时间信息 |
这个坑最危险的地方在于:不是执行报错,而是执行通过但结果悄悄变了。
把 DM8 的 DATE 直接迁成 MySQL 的 DATE,数据能导进去,但时间部分已经没了。Java 原来按 LocalDateTime 读,现在只能读出日期;接口返回值少了时间;报表、审计、排序逻辑都可能被连带影响。
结论 :DM8 的 DATE 迁到 MySQL 或 KingbaseES,本质上应该迁成 DATETIME 或 TIMESTAMP,而不是 DATE。
坑 2:NUMBER 到底该不该压成整数
NUMBER 在 DM8 里是个很灵活的类型:有时是整数,有时是金额,有时只是一个泛数值容器。但到了目标库,这种灵活性会被迫收缩。
在 MySQL 路线里,大量 NUMBER(p,0) 会被压成 INT / BIGINT------技术上看似合理,业务语义上未必正确。一旦压成整数,就等于默认它永远只会是整数型字段。如果后续业务演进需要小数或更宽精度,代价会回到应用层或二次迁移。
这不是一个简单的映射问题,而是一个选择问题:保留语义弹性,还是换取目标库上的局部"自然化"。
坑 3:MySQL 真正的麻烦不是语法,而是限制
很多人低估 DM8 → MySQL 的难度,是因为把注意力放在了"语法像不像"上。实际上,真正的拦路虎是两个硬限制:
- InnoDB 单行大小限制(约 65535 字节)
- InnoDB 索引长度限制(utf8mb4 下约 768 字节 / 191 字符)
这两个限制非常容易在真实结构里叠加出现:单独一个大字段也许没问题,多个大 VARCHAR 放在一起就可能超行大小;复合主键或复合索引叠加上 utf8mb4,索引长度马上膨胀。
一旦被迫把字符串列降级成 TEXT,新的问题又会出现:还能不能参与索引?还能不能参与键?
DM8 → MySQL 往往是一个不断做妥协和折中的过程,而不是字段逐个映射。
坑 4:默认值、序列、触发器,才是真正的业务迁移难点
很多迁移项目会卡在一个看似悖论的阶段:表都建好了,数据也导进去了,但系统还是不能跑。
原因就藏在默认值、序列和触发器里。这三类对象决定的不是"表长什么样",而是"业务怎么活起来"。
典型例子:
- DM8 里的
SEQ_NAME.NEXTVAL + 触发器组合,迁到 MySQL 后天然不存在,必须改成AUTO_INCREMENT或应用生成主键 SYSDATE/SYSTIMESTAMP在源库里用得自然,到了目标库可能根本不接受原样写法OLD/NEW、WHEN子句、体内函数、终止符规则,都可能与目标库不同
如果一篇迁移方案只讲字段映射,不讲默认值、序列、触发器,大概率还没讲到真正的痛点。
坑 5:BLOB / CLOB 往往不是第一天暴露的问题
LOB 是典型的"前期没事,后期炸"问题。
小样本测试看不出来------测试库里字段值不大、数据量也不大,读写链路看起来都没问题;到了真实环境,问题集中出现:
- 大字段读取时驱动缓冲区被截断
- 二进制字面量拼装时踩语法坑(三个目标库写法完全不同)
- 大对象写入策略在目标库里根本不能按预想方式处理
如果项目里有文档、图片、附件、富文本、大 JSON 这类内容,LOB 问题必须被单独拎出来做验证,而不能当成"普通类型映射"的附属部分。
六、一张真实业务表的三路迁移全过程
下面以一张典型业务表为例,把所有坑串起来看一遍。
源表(DM8)
sql
CREATE TABLE ORDER_LOG (
ID NUMBER(19,0) NOT NULL,
BIZ_TIME DATE NOT NULL,
AMOUNT NUMBER(18,2) NOT NULL,
STATUS NUMBER(1,0) DEFAULT 0,
TITLE VARCHAR2(2000 CHAR),
ATTACHMENT BLOB,
CREATED_AT DATE DEFAULT SYSDATE,
PRIMARY KEY (ID)
);
CREATE SEQUENCE SEQ_ORDER_LOG START WITH 1 INCREMENT BY 1;
CREATE OR REPLACE TRIGGER TRG_ORDER_LOG_BI
BEFORE INSERT ON ORDER_LOG
FOR EACH ROW
BEGIN
IF :NEW.ID IS NULL THEN
SELECT SEQ_ORDER_LOG.NEXTVAL INTO :NEW.ID FROM DUAL;
END IF;
END;
这张表看起来很普通,但几乎把 DM8 切库里最常见的坑都带上了:
| 字段 | 涉及的迁移风险 |
|---|---|
ID NUMBER(19,0) |
依赖"序列 + 触发器"发号,不同目标库处理方式完全不同 |
BIZ_TIME DATE |
DM8 的 DATE 含时间语义,迁移后可能静默丢失 |
AMOUNT NUMBER(18,2) |
金额字段,绝不能被压成整数 |
STATUS NUMBER(1,0) |
带默认值,还混有布尔语义风险 |
TITLE VARCHAR2(2000 CHAR) |
字符语义,MySQL 下要评估字节成本和索引参与度 |
ATTACHMENT BLOB |
大二进制对象,三个目标库写法和策略完全不同 |
CREATED_AT DATE DEFAULT SYSDATE |
默认值函数跨库不兼容 |
迁到 MySQL
核心变化点:
ID的"序列 + 触发器"整体替换为AUTO_INCREMENT,主键生成机制改变BIZ_TIME和CREATED_AT按时间语义落到DATETIME,不能照搬DATEAMOUNT明确保留为DECIMAL(18,2),不能因为它是NUMBER就压成整数STATUS NUMBER(1,0)落成TINYINT,需提前判断它是"状态码"还是"真假值"TITLE VARCHAR2(2000 CHAR)在 utf8mb4 下行内成本和索引成本都要重新评估SYSDATE改为 MySQL 可执行的CURRENT_TIMESTAMP
sql
CREATE TABLE ORDER_LOG (
ID BIGINT NOT NULL AUTO_INCREMENT,
BIZ_TIME DATETIME NOT NULL,
AMOUNT DECIMAL(18,2) NOT NULL,
STATUS TINYINT DEFAULT 0,
TITLE VARCHAR(2000),
ATTACHMENT LONGBLOB,
CREATED_AT DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (ID)
);
这张表迁到 MySQL 真正变化的不只是字段语义,而是整套默认值行为和主键生成链路。
迁到 KingbaseES
整体会顺很多,但仍有几个必须处理的点:
ID可继续走序列,也可改成 identity------取决于团队是否想继续维护 sequence + trigger 这套老模式BIZ_TIME和CREATED_AT仍然不能直接落DATE(KingbaseES 的 DATE 也是 date-only),应落TIMESTAMP(0)AMOUNT可平稳落成NUMERIC(18,2),不会像 MySQL 那样出现明显压缩TITLE通常比 MySQL 更容易保留原有长度逻辑,但仍建议做一次边界验证
KingbaseES 路线的核心是"校准语义",而不是"重做结构"。
sql
CREATE TABLE ORDER_LOG (
ID NUMERIC(19,0) NOT NULL,
BIZ_TIME TIMESTAMP(0) NOT NULL,
AMOUNT NUMERIC(18,2) NOT NULL,
STATUS NUMERIC(1,0) DEFAULT 0,
TITLE VARCHAR(2000),
ATTACHMENT BYTEA,
CREATED_AT TIMESTAMP(0) DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (ID)
);
迁到神通(OSCAR)
DDL 看起来越接近"原样迁移",越不能掉以轻心。
ID有机会保留序列与触发器思路,但自增约束和触发器细节必须认真确认,不能因为"都偏 Oracle 风格"就默认兼容BIZ_TIME和CREATED_AT通常比迁到 MySQL/KingbaseES 更自然,但仍需确认应用层的使用方式TITLE VARCHAR2(2000 CHAR)在神通侧要警惕字符/字节语义转换,一旦目标库按字节计长,可能出现长度放大甚至退化为CLOBATTACHMENT BLOB不能只看类型名一致------大对象往往需要额外的分块处理策略
sql
CREATE TABLE ORDER_LOG (
ID NUMBER(19,0) NOT NULL,
BIZ_TIME DATE NOT NULL,
AMOUNT NUMBER(18,2) NOT NULL,
STATUS NUMBER(1,0) DEFAULT 0,
TITLE VARCHAR(2000),
ATTACHMENT BLOB,
CREATED_AT DATE DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (ID)
);
注意:神通这条路线的真正工作量藏在长度语义、触发器细节、BLOB 写入策略这些不显眼的地方------而不是 DDL 本身。
七、一张总表,一句总结
| 维度 | MySQL | KingbaseES | 神通(OSCAR) |
|---|---|---|---|
| 与 DM8 类型语义距离 | 远 | 较近 | 较近 |
DATE 迁移风险 |
高 | 高 | 中 |
NUMBER 处理复杂度 |
高 | 中 | 中 |
| 行大小 / 索引长度压力 | 很高 | 低 | 中 |
| 序列兼容性 | 低 | 高 | 高 |
| 触发器兼容性 | 低 | 中 | 中到高 |
| 大字段迁移复杂度 | 中 | 中 | 高 |
| 总体改造量 | 大 | 中 | 中 |
一句话总结三个方向的核心难点:
MySQL 的问题主要是限制多;KingbaseES 的问题主要是语义校准;神通的问题主要是细节适配。
写在最后
DM8 切库,表面上是数据库替换,实际上是在同时处理三件事:
- 数据类型语义迁移 --- 同名类型不等于同义类型
- 对象模型迁移 --- 序列、触发器、自增是一套联动机制
- 数据写入策略迁移 --- 大字段、默认值、函数行为都要重新对齐
真正做过一次数据库迁移的人,最后通常都会认同这句话:
数据库迁移从来不是类型替换,而是语义迁移。