达梦数据库与Spring Boot/MyBatis-Plus集成实战:主键、分页与兼容性踩坑
1. 达梦数据库的初印象不只是“能用”的国产替代我最早接触达梦数据库是在一个涉及信创改造的项目里。当时客户要求核心系统必须跑在国产数据库上业务侧给的时间又紧我们团队之前没人摸过达梦网上资料又少说实话刚开始压力不小。但真正上手之后发现达梦并没有想象中那么“难搞”它走的是兼容Oracle风格的路线很多SQL语法、函数、包Package习惯都能直接迁移过来对于有过Oracle经验的开发者来说学习曲线比我预想中平滑太多。很多人听到“国产数据库”第一反应是先往性能上质疑。这个观念我个人觉得要更新了。达梦数据库DM在TPC-C这类基准测试里的表现并不差而且它支持行存储、列存储、逻辑备库、读写分离集群大规模联机事务处理OLTP场景下足够稳。加上它对Oracle、MySQL两种语法的双模式兼容让很多存量系统的迁移成本大大降低。这也是为什么在金融、政务、电信这类对数据库稳定性要求极高的行业达梦的出镜率越来越高。这篇文章我打算从两条线来讲一条是达梦数据库的基本指令和它和Oracle/MySQL的差异点另一条是它和Spring Boot MyBatis-Plus这套现代Java技术栈的集成细节。前者解决“会不会用”的问题后者解决“用得顺不顺”的问题。尤其是MyBatis-Plus那一堆自动填充、逻辑删除、分页插件、主键策略换到达梦上之后哪些能直接用哪些得改配置哪些会在运行时悄悄踩坑我都会把实际项目中踩过的点原原本本写出来。不管你是做传统企业级应用的老手还是刚接触国产数据库生态的新人这篇文章应该都能帮你少走不少弯路。我用的一直是达梦8DM8部分特性在DM9/10里可能略有调整但核心思路是通用的。2. 达梦数据库指令核心SQL与模式切换2.1 连接与管理指令从命令行开始达梦的日常管理最常用的工具是自带的管理工具Manager和命令行工具disql。我习惯用disql做快速验证它类似Oracle的sqlplus小巧不占资源。命令行连接的基本格式# 连接本地实例默认端口5236 ./disql SYSDBA/SYSDBAlocalhost:5236 # 如果是在Windows环境 disql SYSDBA/SYSDBAlocalhost:5236连接成功之后有几条指令我几乎每天都会敲-- 查看当前用户 SELECT USER FROM DUAL; -- 查看当前实例名和数据库版本 SELECT INSTANCE_NAME FROM V$INSTANCE; SELECT * FROM V$VERSION; -- 查看所有模式 SELECT DISTINCT OWNER FROM ALL_OBJECTS;这里插一句达梦的DUAL表是真实存在的和Oracle一样你查个常量、算个表达式都可以直接SELECT 11 FROM DUAL;。但如果你是从MySQL转过来的要注意FROM子句不能省略直接SELECT NOW();在达梦里是报错的得写成SELECT NOW() FROM DUAL;这个习惯要刻意练一下不然容易在不该出错的地方浪费五分钟。模式Schema和用户的概念在达梦里和Oracle类似一个用户对应一个默认模式。我们通常说“用户”其实就是在操作该用户的Schema。建用户和授权是这么写的-- 创建用户指定密码规则 CREATE USER APP_USER IDENTIFIED BY App123456; -- 授权允许该用户创建会话、建表 GRANT CREATE SESSION TO APP_USER; GRANT CREATE TABLE TO APP_USER; GRANT UNLIMITED TABLESPACE TO APP_USER;密码我用双引号包起来是因为达梦默认密码复杂度策略要求包含大小写字母和数字某些版本还强制特殊字符。这个策略在初始化实例时可以选择关闭但生产环境我建议保留毕竟数据库安全不能只看业务层。2.2 建表与类型映射和Oracle、MySQL的差异对比达梦的建表语法走的是Oracle的路线但又不是完全照搬。我用一个实际项目里的订单表做例子CREATE TABLE T_ORDER ( ID BIGINT PRIMARY KEY, ORDER_NO VARCHAR(32) NOT NULL, STATUS TINYINT DEFAULT 1, CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UPDATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP, AMOUNT DECIMAL(12,2), REMARK VARCHAR(500) ); COMMENT ON TABLE T_ORDER IS 订单表; COMMENT ON COLUMN T_ORDER.ORDER_NO IS 订单编号;这放在MySQL里看着没什么问题但达梦有几个坑要先讲清楚。第一个坑TINYINT在达梦里是支持的但它的行为更像Oracle的SMALLINT取值范围是-128到127如果说“状态”字段只是0、1、2这种小值用TINYINT完全没问题但一定要注意业务后续会不会超出127。这个我建议直接用SMALLINT或INT省心。第二个坑COMMENT ON TABLE/COLUMN的写法是Oracle风格。如果你用MySQL的ALTER TABLE t_order MODIFY COLUMN order_no VARCHAR(32) COMMENT 订单编号;来加注释达梦不认。一开始团队里有同事习惯用MySQL的注释语法连着报错找了我好几次这里提前提醒大家。第三个坑自增主键。MySQL里最常见的AUTO_INCREMENT在达梦里也有语法支持但底层实现其实是序列SEQUENCE。如果你在建表时写的是ID BIGINT IDENTITY(1,1)那用的就是达梦的IDENTITY自增列如果写ID BIGINT DEFAULT SEQ_ORDER.NEXTVAL就是显式序列。两种方式都行但从后续和MyBatis-Plus配合的角度我更推荐用序列方案原因后面说到主键策略时会展开讲。2.3 常用DML与兼容性陷阱增删改查的核心语法和标准SQL基本一致但有几个函数和行为模式值得专门记一下。字符串拼接达梦支持||也支持CONCAT()。如果你从MySQL迁移过来注意CONCAT(a, b, c)在MySQL里是多个参数拼接达梦的CONCAT()标准写法只收两个参数多个参数要用嵌套CONCAT(CONCAT(a, b), c)。||就没有这个问题建议统一切到||。分页查询达梦支持LIMIT offset, row_count也支持Oracle的ROWNUM写法。这个对从MySQL转过来的人非常友好-- 第10到第20条MySQL风格 SELECT * FROM T_ORDER ORDER BY ID DESC LIMIT 10, 10; -- 等价的Oracle风格 SELECT * FROM ( SELECT T.*, ROWNUM RN FROM ( SELECT * FROM T_ORDER ORDER BY ID DESC ) T WHERE ROWNUM 20 ) WHERE RN 10;实际开发中这两条都不需要手写因为MyBatis-Plus分页插件会帮你生成。但理解底层逻辑很重要因为后续在配置分页插件时dialect类型必须正确否则生成的SQL就会有问题。时间函数用SYSDATE取当前时间NOW()虽然也能用但我更推荐SYSDATE因为这是Oracle风格在达梦的各种版本里兼容性最稳。递归查询达梦用START WITH ... CONNECT BY ...有Oracle经验的人直接上手MySQL 8的WITH RECURSIVE达梦也兼容但对复杂递归的优化不如前者好。大量递归查询场景我建议优先用CONNECT BY写法。3. Spring Boot集成前的基础配置驱动与连接池3.1 驱动坐标与Druid连接池配置达梦官方给Spring Boot提供了JDBC驱动。Maven坐标是dependency groupIdcom.dameng/groupId artifactIdDmJdbcDriver18/artifactId version8.1.2.192/version /dependency注意这个DmJdbcDriver18是针对JDK 1.8的。如果你项目用的JDK 11或17需要找对应的DmJdbcDriver21或者更高版本。这个细节容易忽略我有一次在本地JDK 8编译好的jar包放到生产环境JDK 17跑启动时直接报驱动类异常排查了半天才发现是驱动版本不匹配。application.yml里的核心配置spring: datasource: driver-class-name: dm.jdbc.driver.DmDriver url: jdbc:dm://localhost:5236 username: APP_USER password: App123456 druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 validation-query: SELECT 1 FROM DUAL test-while-idle: true test-on-borrow: false里面最关键的配置是validation-query我写的是SELECT 1 FROM DUAL而不是MySQL的SELECT 1。虽然达梦的兼容性已经做到SELECT 1也能执行成功但连接池每次做存活校验时多用一次隐式转换在高频创建连接的情况下会带来不必要的开销。用DUAL表是最标准的Oracle系写法达梦官方文档也是这么推荐的。test-on-borrow我建议在生产环境设成false改为依赖test-while-idle做定期检测。每次从连接池借出连接都校验一次虽然更安全但高频请求时会影响吞吐实测性能损耗能到3%-5%左右。连接池本身有max-evictable-idle-time机制兜底空闲连接多久没被借出自动关闭重连即可。3.2 常见连接错误与排查顺序连接时报错是最常见的入门问题。我把遇到的几种错误和排查顺序整理一下报错信息根本原因解决办法Network timeout端口不通或防火墙拦截检查5236端口是否可达telnet ip 5236Invalid username or password账号密码错误或密码过期确认密码复杂度和有效期策略必要时由管理员重置No active connection连接池把失效连接标记为不可用检查validation-query是否可执行必要时调大max-waitdriver-class-name类未找到驱动jar包缺失或版本不匹配检查Maven依赖是否成功引入JDK版本是否匹配驱动版本有一个现象我印象很深。某一次应用莫名其妙地在每天凌晨报Connection reset白天又恢复正常。后来查到达梦数据库的会话空闲超时时间是8小时连接池里的连接超过这个时间没有活动就被数据库端强制断开了。应用侧拿着失效连接去执行SQL自然报错。解决方式是把Druid的min-evictable-idle-time调小让连接池在8小时之前就主动把空闲连接清掉。如果有DBA权限也可以调整数据库端的会话超时参数双管齐下最稳。4. MyBatis-Plus对接达梦核心踩坑与配置实战4.1 主键策略IDENTITY、序列还是ASSIGN_ID这是集成过程中第一个要决策的点。MyBatis-Plus默认的主键策略是ASSIGN_ID也就是雪花算法生成一个19位Long型ID。这个策略和数据库无关应用层生成数据库只存值所以达梦用起来没有任何问题。如果你之前的项目就是这么写的那基本不需要改任何东西。但如果你想要数据库自增主键事情就变得有趣了。达梦支持IDENTITY列然而MyBatis-Plus对达梦IDENTITY的兼容存在一个经典问题插入实体后调用getId()返回的仍然是null因为MyBatis-Plus底层通过JDBC的getGeneratedKeys方法取自增主键值达梦对IDENTITY列的getGeneratedKeys支持并不完善。Demo里跑通了生产环境一旦并发上来主键冲突或返回null的情况就可能冒出来。解决办法有两个我推荐第二个。第一个办法是配置KeySequence让MyBatis-Plus走序列方式来获得主键值Data KeySequence(SEQ_ORDER_ID) TableName(T_ORDER) public class OrderEntity { TableId(type IdType.INPUT) private Long id; // ... }注意IdType要选INPUT代表主键值由外部输入序列生成不能再让框架自动生成。实测这样配置后插入语句会在主键字段上直接附上序列的下一个值INSERT INTO T_ORDER (ID, ORDER_NO, ...) VALUES (SEQ_ORDER_ID.NEXTVAL, ?, ...);第二个办法是继续用ASSIGN_ID也就是雪花算法完全绕开数据库序列。这个方案我最后选定了因为雪花ID在分布式场景下天然有序、无冲突也减少了一次序列查询带来的数据库往返。如果你的系统是单库单表规模不大用KeySequence方案也完全没问题看团队习惯。核心点是不要天真地以为IdType.AUTO配置好就能像MySQL那样舒服地自增达梦这边需要多一些额外设计。4.2 逻辑删除与自动填充MyBatis-Plus的能力边界MyBatis-Plus的逻辑删除特性在达梦上是能直接用的。配置方法不区分数据库类型实体字段上加注解Data TableName(T_ORDER) public class OrderEntity { TableLogic TableField(DELETED) private Integer deleted; }然后全局配置里指定逻辑未删除和已删除对应的值mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0框架会在你执行deleteById时自动转为UPDATE t_order SET deleted 1 WHERE id ? AND deleted 0查询时自动追加deleted 0条件。这块上层封装和数据库无关达梦执行标准UPDATE/SELECT没有任何兼容问题。自动填充也一样用MetaObjectHandler全局拦截INSERT和UPDATE操作为创建时间、更新时间、操作人这类字段统一赋值Component public class AuditMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }这里有个细节strictInsertFill和strictUpdateFill只有在字段值本身为null时才会填充如果实体里已经set了值默认不覆盖。如果你希望强制填充要用setFieldValByName。我建议按业务语义选择创建时间字段实体里不手动赋值交给框架统一填。这样即使将来有人换字段名也只是改一处配置不会出现各写各的、数据不统一的情况。4.3 分页插件不走方言直接翻车MyBatis-Plus分页插件和达梦的适配是我认为整篇文章里最值得强调的一个点。很多人第一次集成时按照MySQL的习惯直接写Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }换成达梦时如果忘了把DbType.MYSQL改成DbType.DM问题不是立即报错而是分页SQL在某些边界情况下出现语法错误。原因是分页插件需要根据数据库类型生成对应的分页语句。MySQL是LIMIT offset, size而达梦默认走的是SELECT TOP ...或者带ROWNUM的嵌套子查询写法。方言不对SQL模板就拼接错了。正确配置是这样的Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.DM)); return interceptor; }注意DbType.DM在MyBatis-Plus里的枚举值是存在的不需要自己去拼字符串。配置好之后分页生成的SQL会是类似这样的结构SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( SELECT ID, ORDER_NO, STATUS, CREATE_TIME FROM T_ORDER ORDER BY CREATE_TIME DESC ) TMP WHERE ROWNUM 20 ) WHERE ROW_ID 10这个SQL是达梦能高效执行的典型分页形式。我一度遇到过用DbType.MYSQL但在达梦上少数翻页“碰巧正常”的情况这主要是因为达梦兼容了LIMIT语法但兼容层在执行计划生成上不一定走最优路径翻页深了、数据量大了性能问题就会暴露。所以方言配置一定要一次到位不要抱着侥幸心理。4.4 批量操作与SaveOrUpdate的兼容细节MyBatis-Plus的saveBatch底层是走原生JDBC批量提交rewriteBatchedStatements的达梦的JDBC驱动默认支持批量模式但有没有开启取决于连接参数。这里有一个我实际项目中踩过的坑JDBC URL里不加参数时executeBatch()在达梦上默认是逐条执行的批量插入1000条数据和逐条insert的时间几乎一样白等了。解决办法是在JDBC URL上追加批量处理相关的连接属性。Druid配置里可以这么写spring: datasource: url: jdbc:dm://localhost:5236?batchBatchtrue这个batchBatchtrue参数开启后达梦驱动才会真正把一批预处理语句打包成批量执行。实测开启前后同一批1万条记录的insert耗时从12秒降到2秒左右差距非常明显。不同版本的达梦驱动对这个参数的命名可能有细微差异如果你用的驱动版本较新可以看下官方文档里batchProcess相关描述思路是一样的。saveOrUpdate的兼容性和主键策略强相关。如果主键策略是ASSIGN_ID框架能判断ID是否存在来决定insert还是update如果是IdType.INPUT配合KeySequence插入时会先查序列值再执行insert这个逻辑和达梦也是兼容的。唯一要留意的是如果你在数据库里建了BEFORE INSERT的触发器去自动生成主键MyBatis-Plus并不会感知触发器生成的值实体对象里的ID仍然是null。这种方案我不推荐在MyBatis-Plus里用因为框架层面完全拿不到新主键值后面再做关联插入会很别扭。5. 复杂查询映射、MP注解与通用踩坑清单5.1 LambdaQueryWrapper的“关键字”陷阱用MyBatis-Plus的LambdaQueryWrapper写条件查询时实体字段名会自动转成数据库列名这一层是安全的。真正有问题的是列名本身撞上了达梦的保留字。举个例子我见过有人把字段命名为COMMENT、LEVEL、ORDER、GROUP这些在达梦里全是保留字或函数名。如果建表时没有用双引号包起来SQL执行直接报错就算你用双引号建了表MyBatis-Plus生成的SQL里也没有双引号运行时照样报Syntax Error。两种解决办法第一个字段命名时避开保留字这是最省心的。把列名改成REMARK、ORDER_NO、GROUP_CODE这类一劳永逸。第二个如果表结构是历史遗留改不了那你只能靠自定义SQL在Mapper XML里手写SELECT ORDER FROM T_ORDER注意这里的双引号是达梦区分大小写的标志写的时候要小心。这种情况下LambdaQueryWrapper没法自动加引号强行用就会踩雷。5.2 自增ID回显问题与序列方案对比前面讲到主键策略时候已经提过一句这里再专门补充一个真实场景。我在一个老项目迁移时发现他们原来用MySQL的AUTO_INCREMENT迁移到达梦后简单地把AUTO_INCREMENT字段改成了IDENTITY(1,1)自增本身没问题但MyBatis-Plus插入完拿不到主键。排查过程插入后打印实体对象ID字段一直是null。我一开始怀疑是实体没加TableId检查了没问题。后来在Mapper XML里配置了useGeneratedKeystrue keyPropertyid依然拿不到。最后查到达梦JDBC驱动对getGeneratedKeys的支持存在版本差异有的版本返回主键有的版本返回空。最终解决方案改成序列CREATE SEQUENCE SEQ_ORDER_ID START WITH 10000 INCREMENT BY 1 CACHE 50;实体类加KeySequence主键策略改成INPUT插入完成后ID字段正常回填。走向这个方案还有个额外好处序列可以设置CACHE 50高并发下减少序列访问的数据库往返次数批量插入性能比IDENTITY好。5.3 大小写敏感问题的根源与正确处理达梦建表时如果不加双引号表名和列名默认全部转成大写存储。MyBatis-Plus在生成SQL时列名用的是实体类TableField注解里配置的字段名。如果你在注解里写了小写的列名有两个处境情况一数据库里是用大写存储的而实体注解写的是小写。这时MyBatis-Plus生成的SQL列名都是小写达梦会尝试到数据字典里找小写列找不到就报无效列名。情况二数据库建表时用了双引号强制小写列名实体注解也写了相同的小写列名这时SQL恰好能跑通。但这属于线程般的侥幸因为一旦有人用大写别名做查询立刻就会出问题。最稳妥的做法是建表语句全部不加双引号所有列名统一大写或让数据库自动转大写实体类里的TableField注解按实际列名的大小写准确配置。达梦的DM管理工具里建表结果默认显示大写看到小写列名时要警觉。如果要验证列名实际存储的大小写可以查SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS WHERE TABLE_NAME T_ORDER;结果返回ORDER_NO还是order_no一目了然。5.4 批量foreach插入小心括号与分号陷阱MyBatis-Plus自带的saveBatch好用但有些复杂场景需要手写在XML里写foreach批量插入。这里有个达梦兼容性细节达梦对一条INSERT语句插入多组VALUES的写法支持良好insert idbatchInsert INSERT INTO T_ORDER (ID, ORDER_NO, STATUS) VALUES foreach collectionlist itemitem separator, (#{item.id}, #{item.orderNo}, #{item.status}) /foreach /insert这种写法没问题。但有同学会习惯性地写成INSERT INTO T_ORDER (ID, ORDER_NO, STATUS) foreach collectionlist itemitem separator; VALUES (#{item.id}, #{item.orderNo}, #{item.status}) /foreach用分号分隔多条INSERT语句这在MySQL里能跑通因为驱动开启了allowMultiQueries但达梦的JDBC驱动默认不支持这种多条SQL一起提交的玩法。实测会报Syntax error或者驱动直接拒绝执行。批量操作就认准两种方式saveBatch开启batchBatchtrue或者XML里用单条多VALUES写法。其他花式写法容易把自己坑了。6. 实用排查方法从日志到执行计划6.1 开启MyBatis-Plus SQL日志先定位生成语句集成阶段遇到问题一定是先看MyBatis-Plus生成的SQL长什么样再判断是框架生成问题还是达梦执行问题。配置SQL日志打印logging: level: com.example.mapper: debug注意这里的com.example.mapper要换成你项目里Mapper接口所在的包名。开启后控制台输出Preparing和Parameters两行Preparing就是最终送达数据库的SQL模板。举个例子分页插件配置分页配置错误时控制台Preparing输出的SQL末尾如果跟着一个奇怪的LIMIT ?那明显就是方言配错了如果是带ROWNUM的嵌套子查询那就是正确的达梦方言。6.2 用EXPLAIN确认达梦实际执行计划遇到慢查询不要急着加索引先在达梦管理工具里看一下SQL的执行计划。达梦里执行计划定位和其他数据库差不多EXPLAIN SELECT * FROM T_ORDER WHERE ORDER_NO ABC123;返回结果里重点看两列一是OPERATION就是访问路径比如CSCN2二级索引扫描、CSCN全表扫描、BLKUP2回表二是EST ROWS估算返回行数。如果发现CSCN全表扫描且表数据量很大那说明该建索引了。有个实际案例印象很深分页查询翻到第50页后速度突然变慢执行计划显示每次翻页都扫描了前500条然后丢弃。这就是达梦里用ROWNUM分页的深层嵌套子查询经典问题翻了深页后优化器选择的执行路径不是最优的。最后是在排序列上加了组合索引解决。所以说看到慢SQL别急着甩锅给数据库执行计划会告诉你真实原因。6.3 达梦日志与常见错误代码对照达梦运行时错误会带错误码常用的几个记一下对排查有很大帮助错误代码含义应对思路-6102无法解析的列名列名大小写不匹配检查ALL_TAB_COLUMNS-6106无效的SQL语句语法错误检查保留字与分号-6123违反唯一性约束主键或唯一索引冲突检查序列值或雪花碰撞-6127超出权限缺少建表/查询授权检查GRANT-6005违反非空约束插入NULL值检查实体属性赋值-8027逻辑错误通常是序列相关检查序列是否存在或权限是否足够服务端日志文件存放在达梦安装目录下/log文件夹里实例名.log文件中记录了归档日志和错误堆栈。用dmrman工具SHOW LOG可以按时间范围查看日志归档对于分析某个时间段内数据库崩溃、异常断开的原因很有帮助。7. 实战踩坑记录一次达梦兼容改造的复盘7.1 从SQL Server迁移到达梦的隐藏差异有一次做某医疗系统的数据层改造原系统用的是SQL Server。刚开始信心满满觉得达梦兼容Oracle语法SQL Server的方言差异应该不大。但实际迁移后暴露了一堆类型问题。典型的一个GETDATE()函数。SQL Server里的GETDATE()在达梦里不能直接用得改成SYSDATE或CURRENT_TIMESTAMP。当时通过写一个SQL转换脚本来批量处理字符串替换处理了大约300多处的GETDATE()还有ISNULL替换成达梦对应的NVL或COALESCE。另一个窝火的地方是字符串拼接。SQL Server的拼接在达梦里相当于数值加法运算如果字段是字符串倒还好说但遇到NULL abc在SQL Server里结果是字符串在达梦里结果变成NULL。这种隐蔽行为差异靠编译期查不出来只能靠测试阶段大量造数据来覆盖。所以数据库迁移测试切记要验证字符串空值逻辑惯用的点最容易出问题。7.2 主从复制与读写分离配置思路如果业务量到一定程度单机达梦扛不住需要做读写分离。达梦官方有数据守护Data Watch方案支持实时主备。应用层接达梦时一般不用自己做读写分离因为达梦的JDBC驱动本身不支持类似MySQL那样在连接串上配置读写分离路由需要靠应用层或代理层做。实操中我用的方案是配两个数据源一个指向主库一个指向备库。主库DataSource用于写操作备库DataSource的url指向备机。在Service层通过Transactional路由注解配合AbstractRoutingDataSource动态切换。这种方案需要注意备库的同步延迟读操作如果拿到的是几秒前的数据要对业务做好容忍设计。高一致性要求的读也要路由到主库不能一刀切断到备库。7.3 归档日志膨胀排查达梦在生产上跑一段时间后日志空间占用会越来越大。某次客户现场告警归档日志文件把磁盘占了90%以上。排查后发现是某个批量任务每晚生成大量归档归档保留策略没配置自动清理。查看归档配置SELECT * FROM V$ARCHIVE_DEST_STATUS;如果有大量归档且没有开启自动清理可以调整归档空间限制或者搭配定时备份任务在备份后清理过期归档。达梦自带的dmrman工具可以通过BACKUP DATABASE命令生成备份集并按保留策略管理归档日志。经验是归档日志的保留时长和数据库恢复目标RPO挂钩不是无脑清理。如果业务允许丢失一天的数据那日志保留一天就够了金融类要求分钟级恢复那就得多留磁盘容量规划按这个倒推。8. 常见迁移与兼容性问题速查表我把实际项目里最常遇到的问题汇总成一张表方便大家排查时对照。现象根本原因解决方案插入后拿不到自增主键达梦IDENTITY列的getGeneratedKeys兼容不佳改用序列KeySequence方案或ASSIGN_ID雪花ID分页SQL执行报错PaginationInnerInterceptor方言配置错误改成DbType.DM批量插入慢JDBC URL未开启批量参数连接串上追加batchBatchtrue列名无效或报语法错误列名撞上保留字或大小写不匹配避免保留字统一大写存储TableField准确配置定时任务连接断开数据库会话空闲超时调整连接池空闲回收时间或数据库端会话超时参数SQL Server迁移后时间函数报错GETDATE()、ISNULL等方言不兼容批量替换成SYSDATE、NVL/COALESCEWHERE 11写法失效达梦对某些MySQL语法兼容不彻底改用标准SQL写法避免依赖MySQL宽松语法驱动类找不到JDK版本和驱动版本不匹配换对应JDK版本的DmJdbcDriver长SQL执行极慢嵌套子查询里优化器选择全表扫描用EXPLAIN分析在排序列上建索引DELETE大量数据报错大事务占用回滚段超限分批提交或使用TRUNCATE确认无外键依赖后这张表基本覆盖了我经历过的大部分达梦集成问题。如果一个问题不在表里我的排查思路就四步先看MyBatis-Plus生成SQL对不对再看达梦执行计划走没走对再看驱动版本和URL参数合不合理最后怀疑连接池层面的隐患。大多数坑都能在某个环节水落石出。9. 性能优化与监控达梦的慢SQL定位思路9.1 慢SQL日志开启方法达梦自带慢查询日志开关类似MySQL的slow_query_log。开启方式可以在管理工具里找到性能监控也可以直接用命令-- 开启慢查询监控 SP_SET_PARA_VALUE(1, SVR_LOG, 1); -- 设置阈值单位毫秒 SP_SET_PARA_VALUE(1, SVR_LOG_MIN_EXEC_TIME, 1000);设置以后执行耗时超过1000毫秒的SQL会记录到日志。注意SVR_LOG_MIN_EXEC_TIME的单位在不同版本有差异有文档写的是毫秒有些版本底层是微秒实际测试后以管理工具的提示为准。定位到慢SQL之后用EXPLAIN分析执行计划EXPLAIN SELECT * FROM T_ORDER WHERE CREATE_TIME 2024-01-01;执行计划里关注CSCN全表扫描和CSCN2索引扫描的估算行数。如果大表上过滤字段没有索引计划会显示CSCN全表扫描这时对应的优化动作就是建索引或调整SQL写法。9.2 分页深度翻页的两种优化手段达梦基础分页写法在数据量大、翻页深度高时性能下降明显。比如LIMIT 100000, 20数据库需要把前100020行全部扫描出来再丢弃前10万行。第一种优化手段延迟关联。分页时先在子查询里查询符合条件的主键只查索引覆盖再关联回原表取全部字段SELECT T.* FROM T_ORDER T INNER JOIN ( SELECT ID FROM T_ORDER WHERE CREATE_TIME 2024-01-01 ORDER BY CREATE_TIME DESC LIMIT 100000, 20 ) B ON T.ID B.ID;实测这种写法在深翻页时性能提升明显因为内层子查询只扫描索引不回表每次关联取出20行完整数据。第二种手段游标或Keyset分页。翻页时记录上一页最后一条记录的主键然后下一查询用它过滤SELECT * FROM T_ORDER WHERE CREATE_TIME 2024-01-01 10:00:00 ORDER BY CREATE_TIME DESC LIMIT 20;这种方案彻底告别“跳页”性能最稳定但需要业务方接受“瀑布流式”翻页的交互方式不是所有前端都愿意配合改。For后台管理系统我通常默认先做延迟关联。9.3 归档、备份与监控的日常维护建议达梦数据库的日常维护我建议至少把下面三件事做成定时任务备份使用dmrman命令行工具每天凌晨全量备份归档日志增量备份。备份集存放目录建议单独挂一块磁盘避免和数据文件抢IO。备份命令大致长这样./dmrman CTLSTMTBACKUP DATABASE /data/dm/data/dm8/dm.ini FULL BACKUPSET /backup/full_bak监控磁盘占用达梦数据文件、归档日志、备份集都有可能把磁盘撑满。脚本里定期统计df -h超过70%告警留给业务增长缓冲空间。监控活跃会话查看当前会话以及是否有异常长事务SELECT SELECT USERNAME, STATUS, SQL_TEXT FROM V$SESSIONS;V$SESSIONS里会显示会话状态如果卡在ACTIVE状态时间很长且SQL_TEXT对应一个超大事务需要考虑是否人为终止。终止命令是ALTER SYSTEM KILL SESSION SESSION_ID;这些维护动作虽然是DBA的工作范畴但作为应用开发者了解它们会很有帮助因为很多应用层面看起来像“数据库崩了”的问题在会话视图和日志里往往能找到直接线索。10. 最终建议先跑通一个最小可用Demo达梦和Spring Boot MyBatis-Plus的集成本质上没有特别高深的技术难点。真正消耗精力的是各种兼容性细节在运行时的表现。基于我亲身踩坑的经验建议按下面顺序逐步推进第一步搭一个最小工程只集成达梦数据源和MyBatis-Plus依赖建一张测试表写一个Mapper跑通最基本的select、insert、update、delete。第二步把主键策略和分页插件先配好这两块是最容易埋雷的。跑分页测试时特意翻到第2页、第3页、深翻页确认SQL方言正常因为只翻一页往往发现不了问题。第三步加入业务复杂的逻辑删除、自动填充、批量操作。每个特性加完后跑一遍自动化测试确保行为符合预期。第四步针对慢SQL场景提前造一批大数据量的测试数据用达梦的EXPLAIN验证关键查询的执行计划。发现问题早解决比上线后救火要省钱得多。最后再分享一个小技巧达梦官方社区和文档的更新频率不算快碰到较冷门的问题可以试着用达梦自身的系统视图去找答案比如V$IFUN、V$PARAMETER、V$SESSIONS这些视图的输出信息比搜索零散资料更可靠。国产数据库的生态确实还在完善但实际使用下来配合Spring Boot这套主流技术栈是完全可以工程化的。稳定性、性能、工具链都在逐渐成熟只要把兼容细节处理到位达梦完全能承担核心业务的重任。