最近在做信创项目,把一套跑了七八年的Oracle系统整体迁移到达梦数据库,同时也有几个新项目直接落在达梦上。这段时间写SQL、调SQL、排查连接问题的频率非常高,我把日常用到的高频SQL和踩过的坑整理成一篇总结,给正在接触达梦数据库的朋友做参考。这份总结不会只列“标准答案”,而是把达梦数据库和Oracle、MySQL的差异点、容易报错的地方、以及实际项目中验证过可行的写法一起讲清楚。
如果你是第一次用达梦数据库的开发者或DBA,这篇内容能帮你少走很多弯路;如果你已经在用达梦,也可以对照看看有没有遗漏的细节。达梦的SQL整体兼容性做得不错,但“兼容”不等于“完全一样”,真正上手后你会发现,很多报错都藏在细节里。
1. 达梦数据库的SQL体系与三个先决认知
1.1 兼容模式:Oracle语法和MySQL语法都认,但有个前提
达梦数据库是国内做得比较早的国产关系型数据库,它的SQL语法体系设计上走的是“多兼容”路线。也就是说,你既可以用Oracle风格写SQL,也可以用MySQL风格写SQL,具体由数据库实例的初始化参数决定。最核心的参数是COMPATIBLE_MODE,它决定了数据库在语法解析、函数行为、分页写法上更偏向哪一边。
这个参数在初始化实例的时候配置,后面一般不会去改。实测下来,大多数政企项目初始化达梦时用的是Oracle兼容模式,因为历史系统基本都是Oracle迁过来的,业务代码里Oracle痕迹太重。如果你的环境是MySQL迁过来的,那初始化时就要明确指定兼容MySQL。这里有个很实际的影响:分页SQL写法、字符串拼接符号、部分日期函数,在两种模式下行为不一样。
我在项目中常用的判断方法很简单,执行一句SELECT * FROM v$parameter WHERE name = 'COMPATIBLE_MODE';,看返回的取值是几。0表示Oracle兼容,1表示MySQL兼容,2表示部分兼容。带着这个认知去写SQL,很多“为什么我这么写报错、那么写不报错”的疑惑就能解释通了。
1.2 大小写敏感:这是新手最容易“莫名其妙”报错的地方
达梦数据库默认行为和Oracle一致:未加双引号的标识符,一律转换为大写存储和解析。这句话你要是忽略了,后面会有大量“无效的表名”、“无效的列名”报错在等你。
举个真实例子。开发同事在客户端里执行:
CREATE TABLE user_info ( id INT, user_name VARCHAR(50) );执行成功,看着没问题。然后查询时他写:
SELECT user_name FROM user_info;结果报“无效的表名或视图名”。原因是他用的客户端工具如果设置了保留小写,或者他创建时实际生成的是大写标识符,两条语句对不上。达梦在未加双引号的情况下,把user_info理解为USER_INFO,而表实际是以什么名字存储的,取决于创建时的行为。
实际操作中,我一般建议团队统一约束:建表语句全部用大写,或者全部加双引号并保持大小写一致。尤其从MySQL迁过来的兄弟,MySQL里小写习惯太深,到了达梦很容易在这里翻车。如果你不确定某个对象的实际存储名称,可以查系统视图:
SELECT OBJECT_NAME, OBJECT_TYPE FROM ALL_OBJECTS WHERE OBJECT_TYPE = 'TABLE';看到实际名字再决定SQL怎么写,比在那里干猜快得多。
1.3 模式(SCHEMA):达梦里的“模式错误”一半和它有关
达梦的模式概念和用户关系紧密。默认情况下,每个用户会对应一个同名的模式。如果没有显式指定,你登录后创建的表、视图都放在当前用户对应的模式里。跨模式访问必须带模式名前缀:模式名.表名。
热词里有“达梦数据库 模式错误”,我排查过好几个这样的问题,原因大同小异:A用户创建了表,B用户去查,直接写SELECT * FROM table_name,结果报“无效的表名”或“模式不存在”。因为在B用户的默认模式下根本没这个表。
正确写法是加上模式名。比如你登录的是SYSDBA,但表建在TEST模式下:
SELECT * FROM TEST.USER_INFO;还有一种情况更隐蔽:应用配置里连接的用户名是A,JDBC URL里没加schema属性,但业务SQL里全是B模式下的表,导致应用一启动就各种表找不到。解决办法要么给SQL全加上模式名前缀,要么在连接层面指定默认模式。比如JDBC URL里可以加schema=xxx,Druid数据源里也有对应的配置项。
理解用户、模式、表这三层关系,你就能少走至少一半弯路。记住一句话:先确认你在哪个模式,再确认表在哪个模式,最后再写SQL。
2. 高频基础SQL写法与避坑要点
2.1 建表与自增主键:IDENTITY还是序列
达梦建表语法整体跟Oracle像,但自增列的处理和MySQL、SQL Server更像。常用两种方案:一种是使用IDENTITY自增列,一种是使用序列(SEQUENCE)配合默认值或触发器。
先看IDENTITY的写法:
CREATE TABLE USER_INFO ( ID INT IDENTITY(1,1) PRIMARY KEY, USER_NAME VARCHAR(50), CREATE_TIME DATETIME DEFAULT SYSDATE );这里IDENTITY(1,1)表示从1开始,每次递增1。插入数据时不要手动给ID赋值,让它自己生成:
INSERT INTO USER_INFO (USER_NAME, CREATE_TIME) VALUES ('张三', SYSDATE);序列的写法则更Oracle一些:
CREATE SEQUENCE SEQ_USER_INFO_ID START WITH 1 INCREMENT BY 1;插入时使用序列:
INSERT INTO USER_INFO (ID, USER_NAME, CREATE_TIME) VALUES (SEQ_USER_INFO_ID.NEXTVAL, '张三', SYSDATE);这两种方式在达梦里都支持。如果项目是从Oracle迁过来的,用序列方式改动最小;如果是从MySQL迁过来的,IDENTITY方式更顺手。
需要注意的坑:达梦表字段类型里,MySQL习惯的VARCHAR没问题,但Oracle习惯的VARCHAR2达梦也兼容。日期类型用DATE、DATETIME、TIMESTAMP,按精度需求选择。大字段用CLOB或TEXT,实际迁移时我见过有人直接把MySQL的LONGTEXT搬过来,达梦也能兼容,但建议在表结构确认时顺手规范掉。
2.2 增删改查与NULL的判断:千万别用“= NULL”
增删改查是数据库使用频率最高的一类SQL,但是简单的语句里也有容易翻车的地方。先说查询过滤:
-- 正确写法:判断NULL SELECT * FROM USER_INFO WHERE USER_NAME IS NULL; -- 错误写法:= NULL 永远不返回结果 SELECT * FROM USER_INFO WHERE USER_NAME = NULL;这几乎是所有SQL新手都会踩的坑。在SQL标准里,NULL代表未知值,任何用=或<>与NULL比较的结果都是“未知”,最终不返回任何行。达梦也一样,这个习惯要刻在脑子里。
再说UPDATE和DELETE,这两类语句在生产环境一定要养成先查后改的习惯:
-- 先确认要影响哪些行 SELECT * FROM USER_INFO WHERE ID = 100; -- 再执行更新 UPDATE USER_INFO SET USER_NAME = '李四' WHERE ID = 100; -- 删除前也先看一眼 DELETE FROM USER_INFO WHERE ID = 100;还有一个场景:批量更新时,达梦的MERGE INTO语法很好用,尤其是做数据同步的时候。达梦完全支持Oracle的MERGE写法:
MERGE INTO USER_INFO T USING (SELECT 100 AS ID, '王五' AS USER_NAME FROM DUAL) S ON (T.ID = S.ID) WHEN MATCHED THEN UPDATE SET T.USER_NAME = S.USER_NAME WHEN NOT MATCHED THEN INSERT (ID, USER_NAME) VALUES (S.ID, S.USER_NAME);这个写法在从外部表或临时表同步数据时极其高效,避免了先Select判断再Update或Insert的繁琐流程。
2.3 去重的三种姿势:DISTINCT、GROUP BY、窗口函数
去重是热词里出现频率很高的需求。按我的经验,不同场景用不同姿势,别一味只会DISTINCT。
最简单的是单表多列去重:
SELECT DISTINCT USER_NAME, DEPT_ID FROM USER_INFO;但当你想把去重后的行里的其他字段也查询出来时,DISTINCT就不够用了。比如我想查每个用户最新的联系方式,正确姿势是用窗口函数:
SELECT * FROM ( SELECT T.*, ROW_NUMBER() OVER(PARTITION BY USER_NAME ORDER BY CREATE_TIME DESC) RN FROM USER_INFO T ) WHERE RN = 1;基于去重需求,分组聚合是另一种常用姿势:
SELECT DEPT_ID, COUNT(*) AS USER_CNT, MAX(CREATE_TIME) AS LAST_TIME FROM USER_INFO GROUP BY DEPT_ID;注意坑点:达梦对GROUP BY的字段要求比MySQL严格,SELECT中出现的非聚合列,必须出现在GROUP BY中。这个就是从Oracle带过来的习惯,从MySQL迁移过来的项目需要适应一下。
2.4 分页查询:三种写法在达梦里并存
达梦分页是个特色场景,因为兼容模式不同,支持多种写法。我按项目经验给你列出来,用哪种取决于你们的代码规范和迁移成本。
第一种,Oracle风格的分页,用ROWNUM伪列:
SELECT * FROM ( SELECT T.*, ROWNUM RN FROM (SELECT * FROM USER_INFO ORDER BY ID) T WHERE ROWNUM <= 20 ) WHERE RN > 10;这种写法相对繁琐,但Oracle迁移项目里经常会见到。
第二种,MySQL风格的LIMIT:
SELECT * FROM USER_INFO ORDER BY ID LIMIT 10, 20;注意LIMIT的语义,MySQL习惯LIMIT 偏移量, 行数,达梦也兼容这个用法。如果你在MySQL兼容模式下初始化,这种写法最省事。
第三种,TOP + 子查询:
SELECT TOP 10 * FROM USER_INFO;适合只取前几条的场景,分页时配合子查询也能做。
我的建议:新项目直接统一用LIMIT写法定规范,简单直观。迁移项目保持原有Oracle写法,改动最小。但要注意,在Oracle兼容模式下,LIMIT是否能直接用,取决于达梦版本和初始化参数,我建议先在本机执行一句SELECT * FROM USER_INFO LIMIT 1验证一下,能跑通就放心用,跑不通就老老实实ROWNUM。
2.5 空值处理:NVL、COALESCE怎么选
空值处理在SQL里无处不在。达梦支持Oracle的NVL,也支持SQL标准的COALESCE。日常使用时我按场景选择:
-- NVL:两个参数,第一个为NULL时返回第二个 SELECT NVL(USER_NAME, '未知用户') FROM USER_INFO; -- COALESCE:可以多个参数,从左到右取第一个非NULL值 SELECT COALESCE(PHONE, EMAIL, '无联系方式') FROM USER_INFO;如果只有两个值的兜底判断,用NVL;多个字段“哪个有值用哪个”的场景,用COALESCE灵活得多。还有一个NULLIF:当两个值相等时返回NULL。比如查一个表中某个指标是否为0,写NULLIF(AMOUNT, 0),配合聚合函数就能把0值排除出统计范围。
3. 进阶SQL:函数、窗口函数、序列
3.1 字符串处理的细节:判断数字字符串的三种思路
热词里有一条“db2 sql判断数字字符串函数”,不同数据库实现不同。在达梦里,判断某个字段是否由纯数字组成,我常用三种方式:
第一种,用REGEXP_LIKE正则判断,最直观:
SELECT USER_NAME FROM USER_INFO WHERE REGEXP_LIKE(USER_NAME, '^[0-9]+$');第二种,用TRANSLATE函数替换掉数字字符,看结果是否为NULL或空串:
SELECT USER_NAME FROM USER_INFO WHERE TRANSLATE(USER_NAME, '0123456789', '') IS NULL;第三种,利用数据转换报错的方式不可控,不建议在SQL里直接CAST,因为遇到非法字符会直接报错中断整个查询。
字符串处理的其他常用函数,达梦和Oracle基本一致:LENGTH获取长度,SUBSTR截取子串,REPLACE替换,INSTR查找位置。注意字符串连接符号,Oracle兼容模式下用||:
SELECT USER_NAME || ' - ' || DEPT_NAME FROM USER_INFO;MySQL兼容模式下也可以使用CONCAT(USER_NAME, ' - ', DEPT_NAME)。为了统一,我会在项目里指定一种风格,避免两种写法混用。
3.2 日期时间函数:SYSDATE、TO_DATE和相关计算
达梦的日期函数体系相当完整。当前日期时间的获取,SYSDATE和CURRENT_TIMESTAMP都可用。字符串转日期用TO_DATE:
SELECT TO_DATE('2025-06-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS') FROM DUAL;日期差值计算可以用DATEDIFF:
SELECT DATEDIFF(DAY, CREATE_TIME, SYSDATE) AS DAYS_OLD FROM USER_INFO;也可以使用DATEADD做日期的偏移计算:
SELECT DATEADD(MONTH, -6, SYSDATE) FROM DUAL;按年月日做聚合统计时,用EXTRACT抽取日期分量:
SELECT EXTRACT(YEAR FROM CREATE_TIME) AS Y, EXTRACT(MONTH FROM CREATE_TIME) AS M, COUNT(*) FROM USER_INFO GROUP BY EXTRACT(YEAR FROM CREATE_TIME), EXTRACT(MONTH FROM CREATE_TIME);特别注意:字符串日期比较时,千万别依赖隐式转换。字段是VARCHAR存了'2025-06-01',你把条件写WHERE date_str = TO_DATE('2025-06-01', 'YYYY-MM-DD'),两边类型不一致,要么报错,要么走不了索引。正确做法是同一类型互比:字段如果是日期类型,就用日期和它比;字段如果是字符串类型,就用字符串格式和它比,保持格式统一,例如WHERE date_str >= '2025-06-01'。
3.3 窗口函数:ROW_NUMBER、RANK、DENSE_RANK的应用
窗口函数是处理“分组取TopN”“同组内排名”这类问题的标准答案,达梦对窗口函数的支持已经比较完整。
分组取每个部门最新入职的两个人:
SELECT * FROM ( SELECT E.*, ROW_NUMBER() OVER(PARTITION BY DEPT_ID ORDER BY HIRE_DATE DESC) RN FROM EMPLOYEE E ) WHERE RN <= 2;排名时,RANK和DENSE_RANK在并列排名时行为不同。RANK遇到并列会跳过排名号,比如两个并列第二,下一个就是第四;DENSE_RANK则不跳号,下一个是第三。按业务需求选择。
聚合窗口函数也很好用,比如算累计值:
SELECT DEPT_ID, AMOUNT, SUM(AMOUNT) OVER(PARTITION BY DEPT_ID ORDER BY CREATE_TIME) AS CUM_AMOUNT FROM SALES;这个写法的含义是“同一部门内按时间累加金额”,比在程序里循环快得多,也便于后续做趋势分析。
3.4 序列与自增值处理:NEXTVAL和CURRVAL的正确用法
前面建表部分提到过序列。这里补充几个使用细节。
序列取值用.NEXTVAL和.CURRVAL:
-- 获取下一个值 SELECT SEQ_USER_INFO_ID.NEXTVAL FROM DUAL; -- 获取当前值(必须在本会话中已经至少执行过一次NEXTVAL才能用) SELECT SEQ_USER_INFO_ID.CURRVAL FROM DUAL;在实际项目中,很多场景不一定要把序列直接绑到表的默认值上,而是业务代码里先取一个序列值,再作为主键插入。这种方式对分布式应用也友好,避免依赖数据库的自增机制。
批量插入时如果需要一组连续ID,可以先循环取序列值再拼接语句。但要注意,序列是“取一次跳一次”,就算事务回滚了,已取出的序列值也不会归还。这个特性跟Oracle完全一样,别指望回滚后序列值能连续。达梦的序列缓存大小也是可以配置的,高并发环境下适当调大CACHE可以减少性能损耗。
4. 慢SQL排查与优化实操
4.1 先看执行计划:EXPLAIN是优化第一步
SQL写得再花哨,最终还是要看执行计划来判断好坏。达梦里用EXPLAIN关键字可以查看一条SQL的执行计划:
EXPLAIN SELECT * FROM USER_INFO WHERE DEPT_ID = 10;输出里重点看几类信息:访问方式(是全表扫描 TABLE ACCESS FULL,还是索引扫描 INDEX RANGE SCAN)、连接顺序(先连哪张表再连哪张表)、连接方式(嵌套循环、哈希连接、排序合并)。全表扫描在大表查询中往往是性能瓶颈的头号嫌疑。
如果需要更详细的统计信息和实际执行代价,可以在达梦管理工具或通过EXPLAIN WITH STATISTICS之类的扩展方式看执行过程。平时调优,我的顺序是:先看执行计划有没有全表扫描,再看过滤条件有没有走到索引,最后看排序和连接是否有优化空间。
4.2 索引的建立原则:不是建得越多越好
达梦的索引类型和Oracle大体一致,B树索引是默认主力。建立索引时我遵循这么几条原则:
- 等值查询频繁的字段放最前面,范围查询字段放后面,这是复合索引“最左前缀”的原则。
- 尽量用区分度高的字段做索引,比如用户ID、订单号。性别这种区分度极低的字段,建索引收益很小,反而增加写入开销。
- 复合索引列数量建议控制在3列以内,不是越多越好。
- WHERE条件中的字段如果有函数包裹,比如
WHERE UPPER(USER_NAME) = 'ZHANG',普通索引会失效,这时候要考虑建函数索引。
达梦支持函数索引,这是一个容易被忽视但有奇效的功能:
CREATE INDEX IDX_USER_INFO_UNAME ON USER_INFO(UPPER(USER_NAME));在从MySQL迁到达梦的项目里,函数索引尤其有用,因为很多业务SQL习惯在查询条件里写各种函数。
4.3 并行查询和绑定变量:大表和重复SQL的两种解法
统计大表、跑历史报表时,合理使用并行查询能显著缩短耗时。达梦支持PARALLEL提示,语法和Oracle类似:
SELECT /*+ PARALLEL(4) */ COUNT(*) FROM BIG_SALES_TABLE;但并行不是万能的,并发高的小事务场景开并行反而是负担。我的经验是:只有单条大查询、数据量超过千万级、服务器CPU有富余时,才考虑用并行。
另一个容易被忽略的点是绑定变量。如果应用层不断提交结构相同、只是条件值不同的SQL,达梦每次都要做硬解析,对CPU和共享池都是压力。MyBatis里默认使用#{}占位符就能自动走绑定变量,在达梦上实测有效。顺便说一句,${}拼接方式除了有SQL注入风险,也会导致执行计划无法复用,能不用就不用。
4.4 常见慢SQL场景与改写思路
我总结项目中高频出现的三类慢SQL,以及相应的改写方向。
第一类,OR条件导致索引失效。例如:
SELECT * FROM ORDERS WHERE STATUS = '1' OR STATUS = '2';这种可以改写成UNION ALL:
SELECT * FROM ORDERS WHERE STATUS = '1' UNION ALL SELECT * FROM ORDERS WHERE STATUS = '2';第二类,LIKE '%关键字%'前置模糊匹配,索引走不上。好一点的做法是全文索引,或者把查询条件改成前缀匹配LIKE '关键字%'。如果业务必须前后模糊查,数据量大时建议上搜索组件,或者接受全表扫描的事实。
第三类,大偏移量分页,LIMIT 100000, 20要扫描前面10万行才能取到数据。优化思路是“先取ID,再回表查详情”:
SELECT * FROM ORDERS WHERE ID > (SELECT ID FROM ORDERS ORDER BY ID LIMIT 100000, 1) ORDER BY ID LIMIT 20;这种写法利用主键索引直接跳到目标位置附近,比直接LIMIT大偏移高效得多。
5. 达梦数据库连接与常见报错排查
5.1 驱动、端口与JDBC URL:这些参数别写错
达梦的JDBC驱动类名是dm.jdbc.driver.DmDriver,默认端口是5236,和MySQL的3306、Oracle的1521都不一样,项目配置时容易写错。
JDBC URL的标准格式:
jdbc:dm://127.0.0.1:5236?schema=TEST注意最后的schema=TEST参数,可以用来指定默认模式。如果应用里很多业务表都建在TEST模式下,URL里带上这个参数能省掉很多麻烦。
驱动JAR包在达梦数据库安装目录的drivers/jdbc下有对应文件,比如DmJdbcDriver18.jar。使用Maven时,达梦驱动一般不在中央仓库,需要用install-file命令打到本地仓库,或者私服里单独上传。
5.2 Navicat和IDEA连接达梦的实操
Navicat连接达梦,需要 Navicat Premium 16 及以上版本,连接类型里选择“达梦数据库”。填主机、端口、用户名、密码,测试连接通过即可。这里有个细节:Navicat连接达梦时如果你选择的连接类型不对,会一直提示“无法加载驱动”,先确认版本够新,再看驱动有没有装上。
IDEA连接达梦比Navicat稍微麻烦一点。在IDEA的Database面板里,新建数据源时如果列表里没有达梦,需要手动添加驱动。做法是:在数据源配置界面选择“驱动”Tab,添加一个驱动类,驱动库文件指向你本地下载的DmJdbcDriver.jar,类名填dm.jdbc.driver.DmDriver,URL模板填jdbc:dm://host:port。然后把驱动配置到数据源里,填好账号密码就能连上。
5.3 常见报错速查表
这里把我在项目中遇到过的达梦SQL相关报错和解决思路整理成一张速查表,方便对照排查。
| 报错信息 | 常见原因 | 解决思路 |
|---|---|---|
| 无效的表名或视图名 | 表名大小写不一致,或没有加模式名 | 用大写标识符,或查询ALL_OBJECTS确认实际表名,跨模式加前缀 |
| 无效的列名 | SELECT或WHERE中的列名与表结构不一致 | 确认列名大小写和所属表,注意达梦未加双引号后转大写 |
| 模式不存在 | 连接用户下没有对应模式 | 确认初始化时的模式名,SQL中加模式名.表名 |
| 字符串截断 | 插入的数据长度超出字段定义 | 检查VARCHAR长度定义,必要时改用CLOB或TEXT |
| 仅支持指定级别的空值 | 空值比较语法错误 | 改用IS NULL/IS NOT NULL,不用= NULL |
| 锁等待超时 | 事务未提交,其他会话阻塞 | 排查ACTIVE事务,提交或回滚,调整锁超时配置 |
| 非法的数字 | 字符串转数字失败 | 先清理脏数据,或用REGEXP_LIKE过滤非数字内容后再转换 |
5.4 锁等待和事务问题:一条SQL卡住别慌
达梦的锁等待机制和Oracle总体类似。一个会话更新了某行但没提交,其他会话更新同一行时就会阻塞。排查时可以用达梦的动态视图查询锁信息,比如:
SELECT * FROM V$LOCK;再看哪些事务占用了资源、持有了多久。日常反复出现锁等待的项目,我会重点关注两件事:一是代码里事务是不是开得太久,有没有在事务里做了大量耗时的外部调用;二是连接池连接的数量和获取等待时间是否合理。
另外一个经验:遇到“锁等待超时”不是简单调大超时时间就够了,超时时间调太大只会掩盖问题。真正要做的是找到没提交的事务源头,把事务逻辑改短改小,避免大事务长时间持有锁。
6. 达梦与SpringBoot/MyBatis集成时的SQL兼容性细节
6.1 Druid数据源配置达梦的几个关键点
SpringBoot项目最常用的组合是 MyBatis + Druid + 数据库。切到达梦后,Druid数据源配置里需要指定达梦驱动:
spring: datasource: driver-class-name: dm.jdbc.driver.DmDriver url: jdbc:dm://127.0.0.1:5236?schema=TEST username: TEST password: test123 druid: validation-query: SELECT 1validation-query: SELECT 1这一行很重要。Druid默认的校验SQL可能是SELECT 1 FROM DUAL,在达梦里虽然也能跑,但换成SELECT 1更通用,避免某些兼容模式下不必要的报错。
还要注意Druid的filters配置,如果有wall防火墙过滤器,有些达梦语法可能被误拦截,比如LIMIT、MERGE INTO。遇到“SQL被拦截”的报错,可以把wall过滤器去掉或调整白名单。
6.2 从Oracle迁移到达梦:SQL的兼容性比对
迁移项目最大的痛点是SQL方言。Oracle和达梦虽然是血缘关系最近的一对,但仍有细节差异。我遇到过的最常见几个:
ROWNUM分页在达梦里能用,但如果涉及子查询嵌套的复杂分页,建议直接用窗口函数或LIMIT重写,逻辑更清晰。DECODE函数在达梦里也支持,但嵌套过多时可读性差,建议改成CASE WHEN,两边都兼容且更易维护。SYSDATE两边一致,没问题。但SYSTIMESTAMP的精度差异要注意,涉及时间戳精确到微秒的业务,要在联调时验证。- 自增列:Oracle的
GENERATED BY DEFAULT AS IDENTITY达梦也有类似语法,但如果Oracle侧是用SEQUENCE + TRIGGER实现的,迁移到达梦建议直接用达梦的IDENTITY。 - 字符串类型:Oracle的
VARCHAR2(20)达梦能用;但如果代码里有VARCHAR2(20 CHAR)这样的字符长度语义,注意达梦对字符和字节的处理要与源库核对,避免字段长度不够截断。
我在迁移时给团队定了条规矩:所有SQL先在一个统一的兼容模式测试库上跑一遍,再上测试环境联调。达梦的兼容性总体出色,但“绝大多数”不是“百分之百”,你必须用你自己的SQL去试。
6.3 MyBatis XML里的SQL写法建议
写MyBatis的XML文件时,有几个习惯可以有效减少达梦兼容性问题。
用#{}传参,不要用${}拼接。前面说了,既能预防SQL注入,又能让达梦走绑定变量复用执行计划。
分页插件如果用PageHelper,需要给达梦配置对应的方言。PageHelper官方支持达梦,配置一下dialect: dm就行。如果项目里是自己手写的分页,用LIMIT #{offset}, #{pageSize},注意MySQL模式下LIMIT偏移量和行数的顺序,别写反。
批量插入在达梦上的写法,MySQL式的INSERT INTO ... VALUES (...), (...), (...)在达梦某些版本也能用,但最稳妥的还是用FORALL或者MyBatis的<foreach>拼接多条INSERT ALL INTO ... SELECT ... FROM DUAL写法。实际项目里,数据量超过几百条时,我倾向于使用MyBatis的batch执行器,把ExecutorType.BATCH配上,稳定性更好。
6.4 一个我最近实测通过的案例
最后分享一个实战中确认过的组合。项目用的是SpringBoot 2.7 + MyBatis Plus + Druid + 达梦8。MyBatis Plus的Page分页默认能适配达梦,但前提是PaginationInnerInterceptor里配置了达梦的方言。在配置类里加上:
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.DM);这样生成的LIMIT分页语句才能被达梦正确解析。如果没配,默认按MySQL处理,在某些达梦版本上会报SQL语法错误。
另一个细节是逻辑删除。MyBatis Plus默认的逻辑删除是UPDATE ... SET deleted = 1 WHERE ...,这在达梦上没问题。但如果实体类字段是LocalDateTime类型,MyBatis Plus自动填充功能要求数据库支持对应的日期类型,达梦的TIMESTAMP和DATETIME都行,记得建表时别用长度不合适的类型。
我个人在实际操作中的体会是,达梦数据库整体上手成本比想象中低很多。只要理解了大小写规则、模式概念、分页方言这三个核心点,日常SQL开发几乎不会遇到什么大障碍。真正消耗时间的反而是应用层连接配置和迁移后SQL的细节校验。这个总结是我在实际项目中“踩坑—查证—修复—沉淀”后的结果,希望能帮你在达梦上少走几步冤枉路。你后续如果遇到哪些达梦相关的SQL问题,欢迎带着具体场景再讨论。