国产数据库迁移实战:从MySQL到达梦8的5个坑

4 阅读7分钟

最近半年,因为信创要求,我把一个SpringCloud微服务项目从MySQL8迁移到了达梦8。整个过程比想象中曲折,但也积累了不少经验。

这篇文章不聊大道理,直接说5个我真实踩过的坑和对应的解决方案。希望能帮你少走弯路。

  • 原库:MySQL 8.0.28
  • 目标库:达梦 8.1
  • 框架:SpringBoot 2.x(升级到3.x) + SpringCloud + JPA(Hibernate)
  • 数据量:核心表百万级

坑1:JPA 自动建表失败——大小写敏感 + 字符集冲突

现象

项目启动,ddl-auto: update 报错:项目使用了JPA,并且auto-ddl设置的为update,在连接达梦数据库的时候,第一次启动没有问题,但是后面重启就会报错,发现错误为重复建表,也就是说已经建好的表没有检测到

table or view does not exist

同时看到达梦里创建的表名变成了大写,代码里用的是小写(mysql习惯)。

原因

达梦 8 默认大小写敏感,且默认字符集是 GB18030,而 MySQL 8 默认是 utf8mb4

Hibernate 生成表名时,在不同数据库下行为不一致:

  • MySQL 下保持代码中的大小写
  • 达梦下可能转成大写 解决1:其他配置保持不变,直接贴代码
public class DmStandardTableExporter extends StandardTableExporter {  
  
public DmStandardTableExporter(Dialect dialect) {  
super(dialect);  
}  
  
@Override  
public String[] getSqlCreateStrings(Table table, Metadata metadata, SqlStringGenerationContext context) {  
// 保存原始表名和引用状态  
String originalName = table.getName();  
//判断是否带引号  
boolean wasQuoted = table.isQuoted();  
  
// 仅当表名未被显式引用时转换为大写  
if (!wasQuoted) {  
table.setName(originalName.toUpperCase()); // 统一转为大写  
table.setQuoted(false); // 确保不添加引号  
}  
  
try {  
return super.getSqlCreateStrings(table, metadata, context);  
} finally {  
// 恢复原始状态(避免影响后续操作)  
table.setName(originalName);  
table.setQuoted(wasQuoted);  
}  
}  
}

代码作用

这是一个自定义的Hibernate表结构导出器,用于在自动建表(ddl-auto: update/create)时,将表名统一转成大写,解决达梦8大小写敏感导致的"表不存在"问题。

核心逻辑

1. 为什么要这样做?

  • 达梦8默认大小写敏感
  • 如果代码中表名是 t_user(小写),Hibernate生成的建表SQL可能是 t_user 或 T_USER
  • 达梦里查询时,不加引号默认转成大写去找,导致 t_user 找不到

2. 代码执行流程

// 原始表名:假设是 "t_user"
String originalName = table.getName();           // "t_user"
boolean wasQuoted = table.isQuoted();             // false(没加引号)

// 转成大写
if (!wasQuoted) {
    table.setName(originalName.toUpperCase());    // "T_USER"
    table.setQuoted(false);                       // 确保不加引号
}

// 调用父类生成建表SQL
// 最终生成的SQL是:CREATE TABLE T_USER (...)
String[] sqls = super.getSqlCreateStrings(table, metadata, context);

// 恢复原始表名(重要!避免污染其他地方)
table.setName(originalName);    // 恢复成 "t_user"
table.setQuoted(wasQuoted);      // 恢复成 false

3. 为什么要保存和恢复?

因为Hibernate内部可能多次使用同一个Table对象

  • 第一次生成CREATE TABLE语句
  • 后续可能生成索引、外键约束等

如果不恢复,后续操作会一直用大写表名,可能导致不一致。

4. wasQuoted 的判断很关键

// 如果用户显式写了 @Table(name = ""t_user"")
if (wasQuoted) {
    // 说明用户强制要求小写+带引号,不要转换
}

这允许开发者通过手动加引号来精确控制表名格式。

完整使用方式

1. 注册这个自定义导出器

@Component
public class MyDmDialect extends DmDialect {
    
    public MyDmDialect() {
        super();
    }
    
    @Override
    public Class<? extends TableExporter> getTableExporter() {
        return DmStandardTableExporter.class;  // 使用你的自定义类
    }
}

2. 配置使用自定义方言

spring:
  jpa:
    properties:
      hibernate:
        dialect: com.hwa.support.config.MyDmDialect  # 你的自定义方言

坑2:MySQL 8 的窗口函数迁移失败

现象

MySQL 8 里有这样的 SQL:

SELECT 
    id, 
    name, 
    ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY create_time) AS rn
FROM product;

到达梦 8 里直接报语法错误。

原因

达梦 8 支持窗口函数,但语法细节不同:

  • MySQL 8 的 ROW_NUMBER() 等函数基本兼容 SQL 标准
  • 达梦需要 AS 关键字来定义别名的方式更严格
  • 部分窗口函数(如 FIRST_VALUE)在两个库中的默认行为不一致

解决方案

方案一(推荐):使用 JPA 的 @Subselect 或 @Formula 绕过

@Entity
@Subselect("SELECT id, name, ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY create_time) AS rn FROM product")
public class ProductRank {
    // ...
}

方案二:手动改写达梦兼容写法

-- 达梦 8 正确写法
SELECT 
    id, 
    name, 
    ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY create_time) AS rn
FROM product;

(其实语法一样,但达梦要求 OVER 子句中不能有额外的空格或换行?我遇到过换行就报错的情况,把 SQL 压缩成一行解决)

方案三:用传统聚合 + 自连接替代(性能差,不推荐)

我的建议:迁移前先用工具(如达梦自带的迁移工具 DTS)测试一遍,它会提前报出不兼容的 SQL。

坑3:JSON 函数完全不兼容

现象

MySQL 8 大量使用了 JSON 字段和函数:

SELECT JSON_EXTRACT(extra_info, '$.age') FROM user;
UPDATE user SET extra_info = JSON_SET(extra_info, '$.score', 100);

到达梦 8 里全部报错:JSON_EXTRACT is not a valid function

原因

达梦 8 不支持 MySQL 的 JSON 函数
达梦有 JSON 数据类型,但操作函数完全不同(JSON_GETJSON_SET 等),且语法不兼容。

解决方案

方案一(推荐):迁移前把 JSON 字段拆成普通列

-- 原来 extra_info JSON 存了 age, score, address
-- 改成:
ALTER TABLE user ADD COLUMN age INT;
ALTER TABLE user ADD COLUMN score INT;
ALTER TABLE user ADD COLUMN address VARCHAR(255);

这是最彻底、性能最好的方式。

方案二:在应用层做转换

  • 查询时:把 JSON 字符串查出来,在 Java 里用 Jackson/Gson 解析
  • 更新时:Java 里组装 JSON,再写回数据库
  • 代价:失去数据库内 JSON 函数的能力,性能下降

方案三:写一个 SQL 转换工具
我写了一个简单的 JsonFunctionConverter,自动把 JSON_EXTRACT(col, '$.key') 转成达梦的 JSON_GET(col, 'key')
(有需要可以找我拿)

结论:如果你项目里 JSON 用得多,迁移成本会很高。建议提前评估,能拆列就拆列。

坑4:自增主键 + JPA 的 IDENTITY 策略失效

现象

MySQL 8 里用 AUTO_INCREMENT,JPA 实体:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

迁移到达梦后,插入时报:

null is not allowed for column ID

原因

达梦 8 的自增列有两种实现:

  1. IDENTITY 列(类似 MySQL AUTO_INCREMENT)
  2. SEQUENCE 序列(更灵活)

但达梦的 IDENTITY 和 JPA 的 IDENTITY 策略配合时,必须在建表语句中明确指定自增列
如果表是从 MySQL 迁移过来的(表结构已存在),IDENTITY 属性可能丢失。

解决方案

方案一:重建表时指定 IDENTITY

CREATE TABLE t_order (
    id INT IDENTITY(1,1) PRIMARY KEY,
    order_no VARCHAR(50)
);

方案二(推荐微服务场景):改用 SEQUENCE 策略

@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "order_seq")
    @SequenceGenerator(name = "order_seq", sequenceName = "seq_order_id", allocationSize = 1)
    private Long id;
}

达梦里创建序列:

CREATE SEQUENCE seq_order_id START WITH 1 INCREMENT BY 1;

方案三:迁移后用 ALTER 重新添加 IDENTITY(达梦支持,但操作复杂)

ALTER TABLE t_order MODIFY id IDENTITY(1,1);

坑点:如果你用了 JPA 的 allocationSize 默认值 50,会导致 ID 跳号严重(1, 51, 101...),记得改成 1。

坑5:Hibernate与达梦数据库驱动版本不兼容的典型问题。主要是DmDialect-for-hibernate6-8.jar未能完全实现Hibernate 6所需的IdentityColumnSupport接口。

以下是几种解决方案:

方案1:使用官方推荐的达梦方言(推荐)

达梦官方提供了完整的Hibernate支持:

  1. 下载官方驱动包

    • 从达梦官网下载最新JDBC驱动(dm8-xxx-jdbc18.jar)
    • 通常驱动包中已包含方言实现
  2. 配置正确的方言

    spring:
      jpa:
        database-platform: dm8
        hibernate:
          ddl-auto: update
      datasource:
        driver-class-name: dm.jdbc.driver.DmDriver
        url: jdbc:dm://localhost:5236/your_database
    

方案2:降级或升级驱动版本

  1. 检查版本兼容性

    Spring Boot 3.x → Hibernate 6.x → 需要达梦Hibernate 6方言
    
  2. 尝试不同的驱动组合

    • 使用达梦官方完整包(包含方言)
    • 或尝试Hibernate 5.x + Spring Boot 2.7.x(我未试过)

其他坑(多)

  1. group by 必须全量
  2. POINT()函数。达梦使用POINT(x, y)创建点对象,而MySQL的point()函数是POINT(x, y)。此外,达梦的空间距离计算函数也不同。
  3. navicat编辑的sql特殊字段会添加如name,达梦报错,需要删除

最后

国产化迁移不是“能不能做”的问题,而是“怎么做更快、更稳”的问题。

如果你也在做 MySQL 8 → 达梦 8 的迁移,希望这篇文章能帮你省下几个通宵。

我在成都,承接:

  • MySQL/Oracle → 达梦 8 / 人大金仓 迁移
  • SpringCloud 微服务性能调优、线上故障排查
  • 企业管理系统全栈开发(Java+Vue2)

按项目 / 按天合作都可以,欢迎私聊。

微:[13618067633]