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 事务隔离与控制
达梦默认的事务行为贴近Oracle,DML语句执行后不会自动提交,需要显式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 日期和字符函数对照
日常取系统时间,达梦里用SYSDATE,MySQL的NOW()并不是不能用,但我吃过兼容模式的亏,后来统一用SYSDATE:
SELECT 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里 IFNULL,Oracle里 NVL,达梦两者兼容性都有,但建议统一写NVL:
SELECT 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. 运维排错与性能优化用的达梦SQL
6.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包。
连接串最关键的是驱动类和URL:
jdbc: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?schema=USER01 username: USER01 password: your_password driver-class-name: dm.jdbc.driver.DmDriver type: com.alibaba.druid.pool.DruidDataSource这里有个容易踩的坑:URL里如果写了schema=USER01,而数据库用户本身也是USER01,通常没问题;但如果你是SYSDBA身份连接,又想访问USER01模式,别光在URL里写schema,还要确认SYSDBA是否有访问USER01对象的权限,否则一查表就报“表或视图不存在”。
MyBatis的Mapper里编写达梦SQL时,尽量使用JDBC预编译的#{}参数,不要用${}直接拼接。比如:
<select id="selectByUserName" resultType="com.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 USER02 |
7.4 达梦DISQL里的几个常用命令
开发时我习惯直接开DISQL,比图形界面轻量。几个高频命令记录一下:
-- 连接数据库 disql USER01/password@localhost: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看执行计划,再在测试库跑一遍,确认无误后才会拿到生产环境执行,这套习惯帮我避开了不少次“数据库无响应”的险情。