# DbMigrator — MySQL → 达梦(DM8) 数据迁移工具 单文件 Java 工具(JDK 17 直接运行,无需构建)。结构走 `SHOW CREATE TABLE` 结构化解析生成达梦 DDL,数据走 JDBC 驱动层 `ResultSet → PreparedStatement` 批量直搬,**全程零 SQL 文本转义**(INSERT 转义是 dump 文本转换路线最脆弱的环节,本工具从驱动层消灭它)。 已在本项目测试库实测通过:15 表结构 + 1538 行数据 + 18 外键 + 全部唯一索引,行数/主键水位/大字段/全表指纹校验全部一致。 ## 一、运行命令 ```bash # JDK17 + 两个 JDBC 驱动(m2 仓库已有) MYSQL_JAR=C:/Users/Administrator/.m2/repository/com/mysql/mysql-connector-j/8.4.0/mysql-connector-j-8.4.0.jar DM_JAR=C:/Users/Administrator/.m2/repository/com/dameng/DmJdbcDriver18/8.1.3.62/DmJdbcDriver18-8.1.3.62.jar cd backend-java/tools java -Dfile.encoding=UTF-8 -cp "$MYSQL_JAR;$DM_JAR" DbMigrator.java migrate \ --source-url 'jdbc:mysql://源库:3306/dms?useSSL=false&characterEncoding=utf8' \ --source-user dms_app --source-pwd '密码' \ --target-url jdbc:dm://达梦库:5236 --target-user DMS --target-pwd '密码' \ --schema DMS_MIGTEST --create-schema --drop-existing ``` 三种模式: | 模式 | 说明 | |---|---| | `migrate` | 建结构 + 搬数据 + 建外键 + 自动校验(行数 + 单列数值主键水位) | | `verify` | 只校验,不改动目标库 | | `drop-schema` | 只清空目标模式(先删全部外键再删表,可反复重跑) | 常用参数:`--tables a,b` 只迁指定表;`--exclude c` 排除表;`--batch 1000` 批大小;`--no-verify` 跳过校验;`--err-log FILE` 坏行日志(默认 `migrate_errors.log`)。 **Windows 注意**:若密码含 `!`,在部分 shell(git bash 传参给 java.exe)下引号包裹反而会被转义成 `\!`,此时密码**不加引号**裸写即可;URL 含 `&` 则必须加引号。 ## 二、生产迁移操作步骤 1. **预检**:先 `verify` 跑一遍目标连通;确认源库为 MySQL 8.x。 2. **迁移**:`migrate --create-schema --drop-existing`(幂等,可反复重跑)。 3. **补齐版本差**:本库源数据停在 alembic `0021`(缺 0022 登录锁定的 `failed_login_count`/`locked_until` 列)。Flyway 在达梦默认禁用,需**手工执行** `backend-java/src/main/resources/db/migration/V7__login_lockout.sql`(幂等),并同步更新 `alembic_version`/`flyway_schema_history` 中该库对应的版本记录。 4. **切换**:修改 `.env` 的 `DMS_DATABASE_URL` 指向达梦(jdbc:dm 或 dm+dmPython 形式均可,Java 侧自动互转),重启服务。 5. **回退**:`.env` 切回 MySQL URL 即可(迁移只写目标库,不动源库)。 ## 三、已内建的达梦兼容规则(DM8 8.1.2.128 实测) | MySQL | 达梦处理 | |---|---| | `AUTO_INCREMENT` | `IDENTITY(1,1)`;数据期自动 `SET IDENTITY_INSERT ON/OFF` + 显式列清单 | | 生成列 `IF(cond,1,NULL) VIRTUAL` | `TINYINT AS (CASE WHEN cond THEN 1 END)` | | 含生成列的内联 UNIQUE(-2034) | 后置 `CREATE UNIQUE INDEX` | | **NULL 判重语义差异** | MySQL 任一键列 NULL 即不判重;达梦(Oracle 语义)仅全 NULL 键不判重。对齐方式:全部键成分套同一守卫 CASE,非判重行整体置全 NULL 键 | | 单标记生成列唯一键(全局唯一默认项) | 单成分 CASE 守卫唯一索引 | | `ON DELETE RESTRICT` | 剥离(默认 NO ACTION 语义等价) | | FULLTEXT/SPATIAL/前缀索引 | 跳过/整列降级,输出 WARN | | `datetime(n)`/`timestamp(n)` | `TIMESTAMP(n)` | | `text`/`mediumtext`/`longtext`/`json` | `TEXT`/`CLOB`/`TEXT` | | `tinyint(n)` | `TINYINT`(n 为显示宽度不代表值域);unsigned 才升 SMALLINT | | `enum`/`set` | `VARCHAR(255)` + 告警(枚举约束不迁移,靠 CHECK 或应用层) | | 保留字标识符 | 统一大写,保留字加双引号 | ## 四、韧性机制 - **分页**:单列数值主键用 `WHERE pk > ? ORDER BY pk LIMIT n` 窗口分页(内存友好);复合/无主键表整表加载并 WARN。 - **坏行**:批次失败自动降级逐行抢救,坏行记 `--err-log` 不中断整体(唯一键冲突的行会明确记录 pk)。 - **外键后置**:全部数据就位后再建外键,消除表间迁移顺序依赖。 ## 五、告警处理建议 - `非单列数值主键,整表加载`:版本表/字典表无碍;业务大表出现时评估内存或改用 `--tables` 分批。 - `多列唯一键含可空列…NULL组合重复的数据将冲突`:源库若真存在多行「部分 NULL」重复键(MySQL 合法、达梦冲突),需先在源库清洗,或接受该约束在达梦更严格。 - `UNSIGNED 不被达梦支持`:值域超 INT/BIGINT 上限的数据不会存在时无碍;tinyint/smallint unsigned 已自动升档。