达梦数据库SQL实战:从语法兼容到迁移排错,一份能直接用的速查清单 1. 这份达梦SQL清单能帮你解决哪些问题达梦数据库DM在国内信创和央国企项目里出现的频率越来越高很多原本写惯了Oracle、MySQL的人第一次接触时最大的感受不是功能不够而是“看着面熟写起来处处要小心”。就拿SQL来说达梦确实兼容了大量标准SQL和Oracle语法可它又不完全是Oracle同一个分页写法、同一个日期函数、同一个表名大小写规则在不同初始化参数下可能表现不一样。没搞清楚这些差异就闷头写大概率会被“无效的模式名”“列不存在”这种报错折腾一晚上。我最早接触DM8是在做系统迁移的时候项目要求把一套Oracle ERP的报表逻辑平移到达梦业务表几十张存储过程加上定时任务一堆时间又紧。那段时间我把常用SQL基本挨个踩了一遍慢慢沉淀出一套可以直接“抄”的写法。这篇文章不打算讲安装或者性能调优的大道理就老老实实把日常开发、运维、排障中最常用的达梦SQL整理清楚查数据怎么写、建表改表怎么写、分页用什么方案、日期和字符串函数怎么用、连不上库该查哪几条SQL、项目里接JDBC又该注意什么。适合的人群主要有三类刚接到达梦项目还一脸懵的Java开发从Oracle或MySQL迁库需要快速确认语法兼容性的DBA还有做国产化适配卡在连接配置和常见报错上的实施人员。读这篇文章之前不需要你有达梦基础只要熟悉任意一种SQL就能按目录直接找自己关心的片段。每条SQL我都尽量给到可运行的形式并标注适用场景和容易踩的坑。2. 达梦SQL基础模式与对象定位2.1 模式名加不加差别很大达梦里每个用户会对应一个默认模式SCHEMA如果只建了一个用户登录后直接写表名通常没问题。但一旦项目里有多个用户、多套业务库或者从Oracle迁移来了一批带用户前缀的SQL就很容易卡在模式这块。最常见的报错是“无效的模式名”或者“表或视图不存在”。比如我用SYSDBA登录想去查另一个用户USER01下的表直接写SELECT * FROM T_USER;有时候能查到有时候又报“无效的表名”原因是当前会话所在的默认模式并不是这个表所属的模式。最稳妥的写法就是把模式名带上SELECT * FROM USER01.T_USER;如果不想每条SQL都加前缀可以在会话里切换当前模式。达梦兼容Oracle的写法ALTER SESSION SET CURRENT_SCHEMA USER01;也可以用DISQL命令SET SCHEMA USER01;把当前模式切过去之后下面不带前缀的SQL就都指向USER01了。开发中我建议MyBatis项目里统一用“模式名.表名”去写SQL不要依赖会话级设置因为连接池会把会话复用切来切去容易出交叉问题。2.2 查看用户、表和列信息的基础SQL刚接手一个达梦库第一件事永远是先摸清楚库里到底有哪些对象。下面这几条是我每次必敲的-- 查看所有用户 SELECT USERNAME, ACCOUNT_STATUS FROM ALL_USERS; -- 查看当前用户拥有的表 SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME; -- 查看某个模式下的所有表 SELECT OWNER, TABLE_NAME FROM ALL_TABLES WHERE OWNER USER01 ORDER BY TABLE_NAME; -- 查看表的字段信息 SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, NULLABLE FROM ALL_TAB_COLUMNS WHERE OWNER USER01 AND TABLE_NAME T_USER ORDER BY COLUMN_ID;这些视图名和Oracle很像但注意达梦的一些视图在不同版本里字段会略有差异。如果某条SQL报“视图不存在”或“列不存在”先执行 DESC 或者 SELECT * FROM 视图名 WHERE ROWNUM 1 看下真实字段是什么别死记硬背。2.3 简单的查询脚本模板新手刚开始写达梦SQL可以先把这套“安全模板”背下来至少不会出现方向性错误SELECT T.ID, T.USER_NAME, T.CREATE_TIME FROM USER01.T_USER T WHERE T.STATUS 1 ORDER BY T.CREATE_TIME DESC;这里有个很隐蔽的点就是USER_NAME这种字段尽量写成下划线格式不要写 userName 这种驼峰格式。达梦默认会做大小写转换不带引号的标识符会被转成大写所以 T.USER_NAME 会被解析成 T.USER_NAME而你建表时如果没有对列名加双引号列名实际上也是大写存储在字典里的两者能对上。可一旦你在建表SQL里写了CREATE TABLE T_USER (userId VARCHAR(20));那么这个列名就是大小写敏感的小写 userId等查询时写 T.USERID 或者 T.USER_ID 都会报列无效。这一点坑了很多人。我的经验是建表时老老实实全用大写和下划线查询时不加双引号最省心。3. DDL速查建表、索引、视图与序列3.1 建表时字段类型怎么选达梦的字段类型兼容得比较杂既有Oracle风格的VARCHAR2、NUMBER也有MySQL风格的INT、DATETIME。我建议直截了当地按照“接近Oracle”的习惯去选这样迁Oracle项目时改动最小。CREATE TABLE T_USER ( ID INT NOT NULL, USER_NAME VARCHAR(64) NOT NULL, AGE INT, SALARY NUMBER(10,2), BIRTHDAY DATE, CREATE_TIME DATETIME DEFAULT SYSDATE, REMARK CLOB, PRIMARY KEY (ID) );这里几个选择我展开讲一下主键ID建议用INT数据量大用BIGINT不要省这个空间后续分库分表或者对接BI系统时大主键更稳。VARCHAR和VARCHAR2在达梦里通常是一个含义但如果初始化时开启了兼容Oracle的VARCHAR2语义部分版本会按字节还是按字符算长度需要跟DBA确认一下。一般我们写VARCHAR(64)默认是64个字符但遇到中文长度问题就说明库的语义可能偏字节需要改成VARCHAR2(64 CHAR)。CLOB字段别用VARCHAR去硬扛达梦里超过行大小限制会直接报错特别是存JSON或大段文字时。DATETIME DEFAULT SYSDATE这招很实用插入时就不用手工填时间了。建表后加注释也是基本操作不然一个月后没人看得懂COMMENT ON TABLE T_USER IS 用户信息表; COMMENT ON COLUMN T_USER.USER_NAME IS 用户姓名; COMMENT ON COLUMN T_USER.SALARY IS 月薪单位元;3.2 修改表结构的常用写法业务是迭代的表结构必然要改。达梦的ALTER TABLE语法大体接近Oracle但有些写法得注意列关键字-- 追加字段 ALTER TABLE T_USER ADD COLUMN EMAIL VARCHAR(128); -- 修改字段类型 ALTER TABLE T_USER MODIFY COLUMN EMAIL VARCHAR(256); -- 修改字段默认值 ALTER TABLE T_USER MODIFY COLUMN STATUS INT DEFAULT 0; -- 删除字段 ALTER TABLE T_USER DROP COLUMN STATUS; -- 表改名 ALTER TABLE T_USER RENAME TO T_USER_NEW; -- 列改名 ALTER TABLE T_USER RENAME COLUMN USER_NAME TO REAL_NAME;修改字段类型时有个常见坑如果原字段已经有大量数据VARCHAR长度从64改成128一般没事但改成NUMBER或者DATE这种跨类型操作大概率因为不兼容报错。这时需要新建临时列、拷贝数据、再删旧列或者干脆用新型别建新表后INSERT INTO SELECT。别和数据库硬刚业务数据安全才是第一位的。3.3 索引、视图、序列索引是SQL优化的关键利器。达梦创建索引的语法和Oracle几乎一样-- 普通索引 CREATE INDEX IDX_T_USER_AGE ON T_USER(AGE); -- 唯一索引 CREATE UNIQUE INDEX UK_T_USER_NAME ON T_USER(USER_NAME); -- 组合索引 CREATE INDEX IDX_T_USER_TIME ON T_USER(STATUS, CREATE_TIME DESC);建立索引时不要一句“给所有查询字段都加索引”完事索引建多了写入会变慢还会占空间。我一般在慢SQL出现之后结合执行计划去加索引优先给WHERE条件里高频出现的字段和ORDER BY字段建组合索引。组合索引里字段顺序很关键从左到右匹配把区分度最高的字段放前面。视图和序列也是日常开发少不了的-- 创建视图 CREATE OR REPLACE VIEW V_USER_SALARY AS SELECT T.USER_NAME, T.SALARY FROM T_USER T WHERE T.SALARY 5000; -- 创建序列 CREATE SEQUENCE SEQ_USER_ID START WITH 10000 INCREMENT BY 1 CACHE 20; -- 取序列下一个值 SELECT SEQ_USER_ID.NEXTVAL; -- 取序列当前值 SELECT SEQ_USER_ID.CURRVAL;使用序列时有个细节CURRVAL只有在当前会话已经调用过NEXTVAL之后才有效否则会报错。批量插入数据时序列和分布式系统的高并发ID方案是两回事别在微服务多节点环境里指望单库序列能全局唯一除非你确定所有写入都走这个库。4. DML和事务查询之外必须掌握的部分4.1 INSERT、UPDATE、DELETE的实用写法插入单条记录没什么特别但需要注意时间字段和序列的配合INSERT INTO T_USER(ID, USER_NAME, AGE, CREATE_TIME) VALUES(SEQ_USER_ID.NEXTVAL, 张三, 28, SYSDATE);批量插入时我推荐用INSERT ALL或者INSERT INTO SELECT方式尽量避免在业务代码里逐条拼接INSERT语句既慢又容易被SQL注入风险盯上INSERT INTO T_USER_HIS(ID, USER_NAME, AGE) SELECT ID, USER_NAME, AGE FROM T_USER WHERE STATUS 0;UPDATE语句的条件最好显式用主键或唯一索引。很多新手喜欢写UPDATE T_USER SET AGE 30 WHERE USER_NAME 张三;万一USER_NAME没有唯一约束这次更新就可能波及多条记录得灾后复盘。稳妥做法是UPDATE T_USER SET AGE 30 WHERE ID 1001;DELETE同样如此。需要清表时用TRUNCATE和DELETE要想清楚区别TRUNCATE快但不走事务、不能按条件删、大概率无法回滚DELETE慢但可以配合WHERE和事务回滚。4.2 多表关联更新与删除达梦支持Oracle风格的多表更新语法吗我经验是尽量用MERGE或子查询避免一条UPDATE JOIN写出来报语法错误。推荐方案是把关联结果先查出来再用IN匹配UPDATE T_USER T SET T.DEPT_NAME (SELECT D.DEPT_NAME FROM T_DEPT D WHERE D.DEPT_ID T.DEPT_ID) WHERE EXISTS (SELECT 1 FROM T_DEPT D WHERE D.DEPT_ID T.DEPT_ID);这种写法在Oracle和达梦里都能跑迁移成本最低。删除关联表数据的场景也一样DELETE FROM T_USER WHERE DEPT_ID IN (SELECT DEPT_ID FROM T_DEPT WHERE DEPT_NAME LIKE %临时%);4.3 事务隔离与控制达梦默认的事务行为贴近OracleDML语句执行后不会自动提交需要显式COMMIT。如果你从MySQL过来第一次跑后台定时任务时没提交事务数据看似写进去了另一个连接却看不到这就很容易造成“数据丢了”的假象。-- 开启事务在达梦中 DML 后即自动开启事务 UPDATE T_USER SET SALARY SALARY * 1.05 WHERE DEPT_ID 100; -- 提交 COMMIT; -- 回滚 ROLLBACK;在一个事务里做过多次更新后如果发现某一步执行错尽量别把整个事务回滚掉可以用SAVEPOINT做定点回滚SAVEPOINT SP1; UPDATE T_USER SET AGE 30 WHERE ID 1001; ROLLBACK TO SP1;事务越短越好。项目里出现过同事把大量清洗SQL放一个事务里跑结果跑了半小时锁表锁到前端系统告警。处理大数据量时拆成批提交每批500到1000条既稳又快。5. MySQL/Oracle迁移人员最需要的分页和函数写法5.1 分页别盲目用LIMIT从MySQL迁过来的团队第一句通常就是 SELECT ... LIMIT 0,10结果在达梦里直接报错。虽然达梦部分兼容模式或较新版本能支持LIMIT但为了稳妥我推荐直接用Oracle风格的分页在任何模式下都不会翻车-- 第一种ROWNUM SELECT * FROM ( SELECT T.*, ROWNUM RN FROM T_USER T ORDER BY T.CREATE_TIME DESC ) WHERE RN BETWEEN 1 AND 10;不过ROWNUM是在结果集生成时分配的如果先排序再取ROWNUM内层必须先完成排序。上面这种写法能确保按CREATE_TIME倒序后再取前10条。如果数据量大更推荐用ROW_NUMBER()窗口函数SELECT * FROM ( SELECT T.*, ROW_NUMBER() OVER (ORDER BY T.CREATE_TIME DESC) RN FROM T_USER T ) WHERE RN BETWEEN 1 AND 10;第二种写法可控性更强排序字段还可以随意扩展成多列后续做复杂分页也不怕。5.2 日期和字符函数对照日常取系统时间达梦里用SYSDATEMySQL的NOW()并不是不能用但我吃过兼容模式的亏后来统一用SYSDATESELECT SYSDATE; SELECT TO_CHAR(SYSDATE, YYYY-MM-DD HH24:MI:SS) AS NOW_STR; SELECT TO_DATE(2025-06-01 12:00:00, YYYY-MM-DD HH24:MI:SS) AS D;日期加减也很常用SELECT SYSDATE 1 FROM ...; -- 明天同一时刻 SELECT SYSDATE - 7 FROM ...; -- 七天前 SELECT ADD_MONTHS(SYSDATE, 3) FROM ...; -- 三个月后字符串处理方面达梦和Oracle的兼容度很高-- 字符串拼接 SELECT ABC || DEF; -- 截取 SELECT SUBSTR(ABCDEF, 2, 3); -- 查找位置 SELECT INSTR(ABCDEF, CD); -- 判断是否数字 SELECT CASE WHEN REGEXP_LIKE(12345, ^[0-9]$) THEN 1 ELSE 0 END;这里多提一句REGEXP_LIKE它的正则语法和主流数据库基本一致处理“判断手机号、身份证、金额格式”这类需求特别方便。5.3 空值处理和去重MySQL里 IFNULLOracle里 NVL达梦两者兼容性都有但建议统一写NVLSELECT NVL(AGE, 0) AS AGE_VALUE FROM T_USER; SELECT NVL2(REMARK, 有备注, 无备注) FROM T_USER;去重时除了DISTINCT更常用的是ROW_NUMBER()取最新一条这个场景在报表里极常见。比如每个用户只取最近一条登录记录SELECT * FROM ( SELECT T.*, ROW_NUMBER() OVER (PARTITION BY USER_ID ORDER BY LOGIN_TIME DESC) RN FROM T_LOGIN T ) WHERE RN 1;这招比GROUP BY再取时间最大值要稳定得多不但能拿到用户ID还能把整行登录信息都带出来。6. 运维排错与性能优化用的达梦SQL6.1 查询会话、锁和正在执行的SQL遇到达梦数据库卡顿第一步不是重启而是先看会话和锁。以下几条SQL是我平时排查用得最频繁的-- 查看当前所有会话 SELECT SESS_ID, USER_NAME, SQL_TEXT, STATE, CREATE_TIME FROM V$SESSIONS; -- 查看当前正在等待锁资源的会话 SELECT * FROM V$LOCK; -- 查看被锁对象 SELECT * FROM V$LOCKED_OBJECT;如果发现某个会话长时间处于ACTIVE状态且SQL_TEXT一直不变可以先拿到SESS_ID然后和业务方确认是否在跑大事务。确认是阻塞源后再用系统过程杀掉会话别在生产环境乱杀。不同版本的达梦动态视图字段名略有不同执行前先DESC V$SESSIONS看一下字段列表。6.2 慢SQL定位模板慢SQL优化不是上来就看执行计划而是先在库里找到“谁在慢”。下面这个模板可以直接复用SELECT SQL_TEXT, EXECUTIONS, TOTAL_EXEC_TIME, DISK_READS, BUFFER_GETS FROM V$SQL_AREA WHERE EXECUTIONS 100 ORDER BY TOTAL_EXEC_TIME DESC;跑出来的结果里TOTAL_EXEC_TIME一列基本能让人心里有数。针对单条慢SQL再单独看执行计划EXPLAIN SELECT T.USER_NAME, COUNT(*) FROM T_USER T WHERE T.CREATE_TIME TO_DATE(2025-01-01,YYYY-MM-DD) GROUP BY T.USER_NAME;执行计划里如果看到“全表扫描”且表数据量在百万级以上大概率要补索引。注意EXPLAIN只是评估计划不完全代表真实执行开销必要时可以用实际执行时间对比。6.3 统计信息和分析命令达梦的优化器依赖统计信息。建了一堆索引后查询还是慢有时候就是统计信息陈旧。可以手动收集DBMS_STATS.GATHER_TABLE_STATS(USER01, T_USER); DBMS_STATS.GATHER_INDEX_STATS(USER01, IDX_T_USER_AGE);大表收集统计信息会比较耗时建议放到业务低峰期执行。也可以开启表的自动统计信息任务但自动任务的周期和采样方式要谨慎配置生产环境优先人工控制。6.4 表空间和数据文件检查DBA日常巡检至少要看表空间使用率免得业务跑到一半报表“磁盘空间不足”SELECT TABLESPACE_NAME, STATUS, CONTENTS, EXTENT_MANAGEMENT FROM DBA_TABLESPACES; SELECT FILE_ID, TABLESPACE_NAME, BYTES, AUTOEXTENSIBLE FROM DBA_DATA_FILES;表空间使用率还可以结合FILE_BLOCKS计算但初级排查用上面两条看容量和是否自动扩展就够。7. 开发框架和客户端连接达梦的SQL配置与常见报错7.1 Navicat连接达梦的操作要点现在较新版本的Navicat Premium已经支持达梦新建连接时选择达梦数据库默认端口是5236用户名一般用SYSDBA密码是安装时设置的。如果Navicat版本比较老没有达梦类型可以通过“自定义JDBC驱动”添加指向达梦官方JDBC包。连接串最关键的是驱动类和URLjdbc:dm://127.0.0.1:5236JDBC驱动类名是dm.jdbc.driver.DmDriver一定要用项目对应的JDBC版本不要拿老版本驱动去连新版本数据库否则容易出现“不支持的协议”之类的怪问题。7.2 Spring Boot MyBatis Druid集成达梦配置项目里最常见的技术栈是Spring Boot MyBatis Druid。接入达梦时配置文件和MySQL没有本质区别就是URL、驱动、方言和最后表名前缀要注意。spring: datasource: url: jdbc:dm://127.0.0.1:5236?schemaUSER01 username: USER01 password: your_password driver-class-name: dm.jdbc.driver.DmDriver type: com.alibaba.druid.pool.DruidDataSource这里有个容易踩的坑URL里如果写了schemaUSER01而数据库用户本身也是USER01通常没问题但如果你是SYSDBA身份连接又想访问USER01模式别光在URL里写schema还要确认SYSDBA是否有访问USER01对象的权限否则一查表就报“表或视图不存在”。MyBatis的Mapper里编写达梦SQL时尽量使用JDBC预编译的#{}参数不要用${}直接拼接。比如select idselectByUserName resultTypecom.demo.User SELECT USER_NAME, AGE FROM USER01.T_USER WHERE USER_NAME #{userName} /select这里#{userName}会被预编译成占位符从源头上减少SQL注入风险。而${}会直接把字符串拼进SQL里如果业务上必须用比如动态排序列名就需要用白名单校验。7.3 常见报错排查清单我整理了一份自己常备的排查对照表遇到问题可以先对号入座报错现象大概率原因解决办法无效的表名或视图名当前模式不对加“模式名.表名”或切换schema无效的模式名用户名或模式不存在确认用户是否已创建访问是否授权列无效列名大小写问题或者字段名打错检查DESC表结构确认列名第X行附近出现错误SQL语法兼容性问题把Oracle/MySQL特有写法换掉比如LIMIT数据溢出或长度超限字段长度不够修改字段为VARCHAR2或加大长度驱动无法连接端口或驱动版本不对确认5236端口开放JDBC版本匹配无法获取连接MAXCONN不足或连接池配置过高调低Druid最大连接数适当加大数据库会话数表或视图不存在但实际存在权限问题授权查表GRANT SELECT ON USER01.T_USER TO USER027.4 达梦DISQL里的几个常用命令开发时我习惯直接开DISQL比图形界面轻量。几个高频命令记录一下-- 连接数据库 disql USER01/passwordlocalhost:5236 -- 查看当前用户 SELECT USER; -- 显示表结构 DESC T_USER; -- 导入SQL脚本 START /tmp/test.sql; -- 设置每页行数 SET PAGESIZE 200;脚本导入时如果文件里有大量中文注释注意文件编码尽量UTF-8否则DISQL可能把注释里的中文变成乱码甚至影响执行。这是我踩过的实实在在的坑导脚本前先用记事本另存为UTF-8无BOM总没错。8. 几个容易被忽略的达梦SQL细节8.1 双引号与保留字达梦有很多保留字比如LEVEL、TYPE、COMMENT、USER、MATCH等。如果业务表名或字段名不幸起了这种名字不能直接用得加双引号但这会带来大小写敏感的后续问题。我建议遇到保留字就干脆把表名改掉不要贪图名称简短给自己挖坑。如果改不了必须加双引号那建表、查询、JPA映射的每一处都得严格一致。8.2 数据字典查询的权限普通业务账号通常只能看到自己有权限的数据字典。比如用非管理账号查ALL_TABLES返回结果往往不全这不代表表不存在而是权限不足。查不到对象时先看当前账号是否被授予了DICTIONARY访问权限再让DBA确认授权范围。避免拿一个业务账号去验证另一个业务账号的库表结构结果误导排查方向。8.3 批量操作的提交策略定时任务里如果要清洗千万级数据不要一把梭提交最好按主键范围分批处理。比如每次取1万条ID区间处理完提交一次既能避免长事务锁定又能让日志输出进度。实际经验是一个超过千万行的UPDATE一次性提交很可能会拖垮整个库影响在线业务。SELECT和DML的日常写法虽然看着简单但在国产数据库上写“稳”比写“炫”更重要。我今天给出的这些SQL都是经过真实项目和线上环境验证过的不敢说覆盖所有DM版本但覆盖常见的调试、开发、迁移场景足够了。最后再分享一个小习惯我每次写到达梦的疑难SQL都会先用EXPLAIN看执行计划再在测试库跑一遍确认无误后才会拿到生产环境执行这套习惯帮我避开了不少次“数据库无响应”的险情。