最近这半年,国产数据库的讨论度比我前几年加起来还高,尤其达梦DM8在政企和金融项目里的出镜率明显提升。我一开始接手达梦项目时,想法很简单:达梦不是号称兼容Oracle么,那原来的SQL直接搬过来不就行了?结果第一天建用户、建表空间就发现了不少和Oracle不完全一致的地方,更不用说后来在去重、分页、NULL处理上踩的坑。这篇就把我在达梦项目里实际用到的常用SQL整理出来,从环境查询、表空间/用户/权限,到表结构/索引/序列,再到日常DML、查询去重分页,最后是Oracle和MySQL迁过来的适配经验,全部带示例展开。适合正在做达梦开发、运维,或者准备做数据库迁移的朋友,看完可以直接对照着改你手上的SQL。
1. 达梦数据库的语法定位:别把它当成"另一个Oracle",也别当MySQL
1.1 兼容模式决定了很多SQL能不能直接跑
达梦的定位是对标Oracle,官方资料里也明确写了兼容Oracle的主流语法。但"主流语法"这几个字很微妙——它不是说Oracle能跑的到达梦上一定能跑。DM8在初始化实例的时候会有一个兼容模式参数,决定底层行为更接近Oracle还是MySQL。我们项目里用的是默认Oracle兼容模式,所以后面大部分写法都是按Oracle风格来的。如果你手头是MySQL兼容模式,默认大小写、字符串空值处理这些行为都会有出入,最好先确认实例参数再动SQL。
实际工作中,我建议接手任何一台达梦服务器,第一步先执行这条SQL确认版本和环境:
SELECT * FROM V$VERSION;版本号、服务器字符集、实例参数都能确认。很多时候"为什么我这个语法跑不过"的答案,就藏在实例参数配置里。
还要提醒一点:无论什么兼容模式,达梦在标识符大小写、空字符串处理上都和Oracle有细微差别。下一节先把达梦的"库-用户-模式"这套概念讲清楚,后面写SQL才不容易懵。
1.2 达梦里几个容易混淆的基础概念
很多从MySQL过来的同学,一开始会被达梦的"用户即模式"绕晕。在达梦里,一个用户登录之后,默认看到的就是和用户名同名的模式下的对象。简单理解:
- 实例:约等于一套完整的数据库服务,默认端口是5236;
- 用户:登录主体,SYSDBA是管理员账号;
- 模式(SCHEMA):对象的集合,默认和登录账号同名。
举个例子,我在达梦里建一个用户叫 APPUSER,再建了一张表 T_EMP,那这张表的完整路径就是 APPUSER.T_EMP。如果当前登录用户就是 APPUSER,直接写 T_EMP 就能查,不用加前缀。如果换了别的账号登录,不加前缀就会提示"无效的表名"。
这一点在日常写SQL时影响不大,但在做数据迁移、写存储过程、用第三方工具连接时,会遇到"为什么明明建了表却查不到"的问题,多半就是用户和模式对不上。
连接方式方面,最常用的就是达梦自带的DM管理工具和命令行disql。Navicat和DBeaver新版本也能识别达梦,配置连接时IP和端口写5236,驱动选达梦即可。Java技术栈的项目接入方式和MySQL几乎一样,达梦提供了标准的JDBC驱动:
spring: datasource: driver-class-name: dm.jdbc.driver.DmDriver url: jdbc:dm://127.0.0.1:5236 username: APPUSER password: App_2024Druid连接池天然兼容这个驱动,所以从MySQL切到达梦时,多数应用层配置不需要大改,主要改动集中在SQL语句层。
我把达梦当成"一个长得很像Oracle、但又有自己脾气的数据库"来对待,这是在它上面干活最不容易出错的姿势。它的系统视图、数据字典大量复用了Oracle命名,比如 USER_TABLES、DBA_TABLESPACES、V$VERSION这些,Oracle DBA迁移过来几乎没有学习成本。同时它又允许你用 MySQL 的 LIMIT 做分页。这种"两边都像"的特性,落到具体问题上,既不能全按Oracle手册说,也不能全按MySQL经验猜,最靠谱的方式是实测。
2. 上手先摸清环境:库、表空间、用户和权限的常用SQL
2.1 查看数据库版本、当前用户与会话
接手一台达梦服务器,第一件事不是建表,而是把环境看清楚。我常用这几条:
-- 查看达梦版本信息 SELECT * FROM V$VERSION; -- 查看当前登录用户 SELECT USER; -- 查看当前数据库名 SELECT NAME FROM V$DATABASE; -- 查看当前用户下的所有表 SELECT TABLE_NAME FROM USER_TABLES; -- 查看指定模式下的表 SELECT OWNER, TABLE_NAME FROM ALL_TABLES WHERE OWNER = 'APPUSER';这里有个小技巧:USER_TABLES 只返回当前用户模式下能看到的表,但如果用SYSDBA登录,直接查 ALL_TABLES 会返回一大堆系统表,结果动辄几百行。所以查 ALL_TABLES 一定要加 OWNER 过滤,否则看列表看到眼晕。
2.2 表空间的查看与创建
表空间是达梦存储的物理载体,概念和Oracle基本一致。我常用的查看语句:
-- 查看所有表空间 SELECT TABLESPACE_NAME, STATUS FROM DBA_TABLESPACES; -- 查看表空间对应的数据文件 SELECT * FROM DBA_DATA_FILES;创建表空间时,最需要注意的是数据文件路径必须真实存在。达梦安装默认数据目录一般在 /dm8/data/实例名/ 下,像这样写:
CREATE TABLESPACE TS_APP DATAFILE '/dm8/data/DMSERVER/TS_APP.DBF' SIZE 128M AUTOEXTEND ON NEXT 64M MAXSIZE 4096M;如果后续空间不够,我一般直接追加数据文件,而不是改原文件大小:
ALTER TABLESPACE TS_APP ADD DATAFILE '/dm8/data/DMSERVER/TS_APP02.DBF' SIZE 128M;这里补充一个重要提示:在管理工具里执行创建表空间前,最好先用操作系统命令确认目录存在且DM进程对该目录有写权限。否则会报错,而且报错信息有时候并不直观,可能绕一大圈才定位到是目录权限问题。
2.3 用户管理与授权语句
建用户和授权,是所有项目初始化时必走的环节。达梦的默认安全策略比Oracle更严格,密码默认需要满足复杂度要求。我在建账号时会这样写:
-- 创建用户,指定默认表空间 CREATE USER APPUSER IDENTIFIED BY "App_2024" DEFAULT TABLESPACE TS_APP; -- 修改用户密码 ALTER USER APPUSER IDENTIFIED BY "App_2025"; -- 锁定/解锁用户 ALTER USER APPUSER ACCOUNT LOCK; ALTER USER APPUSER ACCOUNT UNLOCK; -- 给用户授予DBA角色 GRANT DBA TO APPUSER; -- 回收权限 REVOKE DBA FROM APPUSER;实际项目中我不推荐一上来就给别人 GRANT DBA,虽然这样开发省事,但生产环境治理起来很麻烦。更合理的做法是按需授权:
GRANT SELECT, INSERT, UPDATE, DELETE ON APPUSER.T_EMP TO READONLY_USER; GRANT CREATE SESSION TO NEW_USER;达梦的常见角色按场景选择,参考如下:
| 角色 | 作用 | 适用场景 |
|---|---|---|
| DBA | 数据库管理员全部权限 | 初始化、紧急变更 |
| RESOURCE | 可以在自己的模式里建对象 | 开发账号 |
| CONNECT | 允许登录数据库 | 只读查询账号 |
权限粒度控制好,后面做审计和数据安全就有抓手。
3. 表结构与索引:建表、改表、索引与序列的惯性写法
3.1 建表时的类型与自增列
达梦建表的语法接近Oracle,但有一些特殊写法。我们项目里最常见的一张表结构大概是这样的:
CREATE TABLE APPUSER.T_EMP ( EMP_ID INT IDENTITY(1, 1), EMP_NO VARCHAR(20) NOT NULL, EMP_NAME VARCHAR(50) NOT NULL, DEPT_ID INT, SALARY NUMBER(10, 2), HIRE_DATE DATE DEFAULT SYSDATE, REMARK CLOB ); COMMENT ON TABLE APPUSER.T_EMP IS '员工表'; COMMENT ON COLUMN APPUSER.T_EMP.EMP_NAME IS '员工姓名';这里三个点值得展开:
- IDENTITY(1,1)是达梦里最常见的自增列写法,和SQL Server类似。如果你用惯了Oracle,会下意识写序列加触发器,但达梦里直接用 IDENTITY 更省事。
- VARCHAR 和 VARCHAR2在达梦中等价,所以老代码里写 VARCHAR2 也能建表。
- DATE 类型在达梦里可以带时分秒。所以从MySQL迁过来的 TIMESTAMP 字段,到达梦之后很多人直接映射成 DATE,结果发现时间也完整保留,省了一道转换。
3.2 修改表结构的常见需求
上线之后的表结构变更,基本集中在加字段、改字段长度、删冗余列。我常用的语句:
-- 增加字段 ALTER TABLE APPUSER.T_EMP ADD (EMP_EMAIL VARCHAR(100)); -- 修改字段类型或长度 ALTER TABLE APPUSER.T_EMP MODIFY (EMP_NAME VARCHAR(100)); -- 修改字段名 ALTER TABLE APPUSER.T_EMP RENAME COLUMN EMP_EMAIL TO EMAIL; -- 删除字段 ALTER TABLE APPUSER.T_EMP DROP COLUMN EMAIL; -- 表重命名 ALTER TABLE APPUSER.T_EMP RENAME TO T_EMPLOYEE;加约束时,命名规则建议一开始就统一好,否则后面查哪个约束是干什么的只能靠猜:
ALTER TABLE APPUSER.T_EMPLOYEE ADD CONSTRAINT PK_T_EMPLOYEE PRIMARY KEY (EMP_ID); ALTER TABLE APPUSER.T_EMPLOYEE ADD CONSTRAINT FK_T_EMPLOYEE_DEPT FOREIGN KEY (DEPT_ID) REFERENCES APPUSER.T_DEPT(DEPT_ID); ALTER TABLE APPUSER.T_EMPLOYEE DROP CONSTRAINT PK_T_EMPLOYEE;我踩过一个坑:在达梦里直接 DROP COLUMN 时,如果这个字段上有索引或者约束,可能会报"被引用"之类的错误。稳妥的做法是先删掉关联的索引和约束,再删字段,顺序不能反。
3.3 索引与序列
索引是慢查询的第一道防线。达梦建索引和Oracle几乎一模一样:
-- 普通索引 CREATE INDEX IDX_EMP_DEPT ON APPUSER.T_EMPLOYEE(DEPT_ID); -- 唯一索引 CREATE UNIQUE INDEX UK_EMP_NO ON APPUSER.T_EMPLOYEE(EMP_NO); -- 复合索引 CREATE INDEX IDX_EMP_DEPT_NAME ON APPUSER.T_EMPLOYEE(DEPT_ID, EMP_NAME); -- 查看用户下的索引 SELECT INDEX_NAME, TABLE_NAME, UNIQUENESS FROM USER_INDEXES WHERE TABLE_NAME = 'T_EMPLOYEE';序列在达梦里的使用也非常普遍,尤其是Oracle迁移项目里老代码大量依赖序列:
CREATE SEQUENCE SEQ_EMP START WITH 1 INCREMENT BY 1 CACHE 20; -- 取下一个值 SELECT SEQ_EMP.NEXTVAL FROM DUAL; -- 取当前值 SELECT SEQ_EMP.CURRVAL FROM DUAL;注意 CURRVAL 必须在当前会话里先执行过 NEXTVAL,否则会报"序列尚未在此会话中定义"。这个限制和Oracle一样,属于老规矩了。
4. 日常DML的实用写法:插入、修改、删除中的差异点
4.1 插入:普通插入、批量插入与MERGE
单条插入在达梦里几乎没有门槛:
INSERT INTO APPUSER.T_EMPLOYEE (EMP_NO, EMP_NAME, DEPT_ID, SALARY, HIRE_DATE) VALUES ('E1001', '张三', 10, 15000, SYSDATE);批量插入时,达梦支持在一条 INSERT 后跟多个 VALUES 子句:
INSERT INTO APPUSER.T_EMPLOYEE (EMP_NO, EMP_NAME, DEPT_ID) VALUES ('E1002', '李四', 10), ('E1003', '王五', 20), ('E1004', '赵六', 20);不过我希望你记住:批量插入的 VALUES 数量不要无脑堆到几千行,达梦有参数控制,堆太多容易触发"记录太长"或事务日志过大的问题。稳妥的做法是每批500行左右分多次提交,尤其在用SQL脚本做数据加载时,这个习惯能避免很多次导入中途失败。
真正让我觉得达梦好用的是 MERGE INTO,也就是"存在则更新,不存在则插入"。做增量数据同步时它太实用了:
MERGE INTO APPUSER.T_EMPLOYEE t USING (SELECT 'E1001' AS EMP_NO, '张三丰' AS EMP_NAME, 10 AS DEPT_ID FROM DUAL) s ON (t.EMP_NO = s.EMP_NO) WHEN MATCHED THEN UPDATE SET t.EMP_NAME = s.EMP_NAME, t.DEPT_ID = s.DEPT_ID WHEN NOT MATCHED THEN INSERT (EMP_NO, EMP_NAME, DEPT_ID) VALUES (s.EMP_NO, s.EMP_NAME, s.DEPT_ID);这个语法和Oracle基本一致,从Oracle迁移过来几乎不用改。我每次做接口表同步、日终跑批更新,都是靠MERGE撑起来的。
4.2 修改与删除
UPDATE 和 DELETE 在日常开发里没什么特殊,但联表更新是很多人第一个翻车点。比如要根据部门名称更新员工的部门ID,Oracle写子查询,达梦也支持:
UPDATE APPUSER.T_EMPLOYEE t SET t.DEPT_ID = (SELECT d.DEPT_ID FROM APPUSER.T_DEPT d WHERE d.DEPT_NAME = '技术部') WHERE EXISTS (SELECT 1 FROM APPUSER.T_DEPT d WHERE d.DEPT_NAME = '技术部' AND d.DEPT_ID = t.DEPT_ID);要注意的是,达梦对更新语句里同时关联被更新表的逻辑有自己的限制。如果 UPDATE 的子查询里引用到了正在更新的表,很容易报"不能更新视图"或"数据被修改"的错误。我一般先把要更新的数据捞到临时表,再基于临时表更新。虽然多了一步,但逻辑清晰,也不容易碰到达梦的边界。
DELETE 删除大批量数据时另有一个性能问题:达梦和Oracle一样,DELETE 不会立刻释放空间,删除1000万行后表空间占用并不会下降。如果目标是把数据清掉并回收空间,直接用 TRUNCATE TABLE 更合适,注意 TRUNCATE 不能回滚。
4.3 高频函数的选用
函数这块基本是Oracle用户的无痛区,达梦继承了绝大部分常用函数。我最常用的是这几组:
- 空值处理:NVL(expr, default) 和 NVL2(expr, v1, v2)。注意 IFNULL 在达梦里也能用,但老代码统一写 NVL 更干净;
- 字符串:SUBSTR、INSTR、REPLACE、LENGTH,字符串拼接用 ||,但要注意任何一边为NULL结果就是NULL,建议用 NVL 包一下;
- 日期:SYSDATE 取当前时间,TO_CHAR 做格式化,TO_DATE 把字符串转日期,ADD_MONTHS 做月份加减;
- 转换:TO_NUMBER 把字符串转数字,CAST 也支持。
举两个实际写法:
-- 拼接员工编号和姓名,对NULL做兜底 SELECT EMP_NO || '-' || NVL(EMP_NAME, '未知') AS EMP_INFO FROM APPUSER.T_EMPLOYEE; -- 最近三个月入职的员工 SELECT * FROM APPUSER.T_EMPLOYEE WHERE HIRE_DATE >= ADD_MONTHS(SYSDATE, -3);这些函数在从Oracle迁移时基本不用改,算是达梦兼容性做得比较好的一块。
5. 查询进阶:分页、去重、连表与慢SQL定位
5.1 分页查询的三种写法
达梦分页的写法很灵活,因为兼容性好,所以Oracle风格、MySQL风格、标准SQL风格都能跑。我按推荐程度排一下。
第一种,Oracle风格的ROWNUM,适合老项目迁移:
SELECT * FROM ( SELECT t.*, ROWNUM AS RN FROM APPUSER.T_EMPLOYEE t ORDER BY t.EMP_ID ) WHERE RN > 10 AND RN <= 20;第二种,标准SQL的OFFSET/FETCH,语义清晰,是目前我最推荐的写法:
SELECT * FROM APPUSER.T_EMPLOYEE ORDER BY EMP_ID OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;第三种,MySQL风格的LIMIT,达梦也认识:
SELECT * FROM APPUSER.T_EMPLOYEE ORDER BY EMP_ID LIMIT 10 OFFSET 10;三种写法在很多场景下等价,但遇到排序字段有大量重复值、或者分页页数很深时,性能差别会显现。我的经验是:小表随便写,大表分页一定要结合索引,并且避免在分页SQL里做全表排序。如果 ORDER BY 字段有索引,用游标分页方式性能可以提升一个量级。
5.2 去重场景:DISTINCT与窗口函数去重
去重是达梦项目里最常被问到的SQL需求,至少有三个层面。
第一层,简单去重,直接 DISTINCT:
SELECT DISTINCT DEPT_ID FROM APPUSER.T_EMPLOYEE; SELECT DISTINCT DEPT_ID, EMP_NAME FROM APPUSER.T_EMPLOYEE;多列 DISTINCT 很多人以为只对第一列去重,其实 DISTINCT 是对后面所有字段的组合去重。这个一定要理解清楚,否则统计结果会跟你预期差很多。
第二层,去重后保留指定记录。比如员工表里同一个员工编号有多条记录(历史调动),我要保留每个员工最新的一条。窗口函数是最标准的解法:
SELECT * FROM ( SELECT t.*, ROW_NUMBER() OVER (PARTITION BY t.EMP_NO ORDER BY t.HIRE_DATE DESC) AS RN FROM APPUSER.T_EMPLOYEE t ) WHERE RN = 1;PARTITION BY 就是"按什么字段分组去重",ORDER BY 决定保留哪一条。这里我用的是 ROW_NUMBER,它会给每个分组从1开始编号,所以 RN=1 就是分组内的第一条。
第三层,去重后统计,比如每个部门不重名的员工数:
SELECT DEPT_ID, COUNT(DISTINCT EMP_NAME) AS CNT FROM APPUSER.T_EMPLOYEE GROUP BY DEPT_ID;对比一下 DISTINCT 和窗口函数的选择:如果只是看结果、不需要保留明细,用 DISTINCT 和 GROUP BY;如果需要去重后还保留整行的其他字段,窗口函数是首选。GROUP BY 加 MAX/MIN 只能保留被聚合的字段,想要展开原行就很麻烦了。
5.3 连表查询与EXISTS
连表查询和Oracle的差异不大,内连接、左连接直接写 JOIN ON 就行:
SELECT t.EMP_NO, t.EMP_NAME, d.DEPT_NAME FROM APPUSER.T_EMPLOYEE t LEFT JOIN APPUSER.T_DEPT d ON t.DEPT_ID = d.DEPT_ID;Oracle老代码里用 (+) 表示外连接,达梦默认也兼容,但我建议新代码不要用这种写法,维护成本高,而且一旦和其他 JOIN 混用,语义很容易错。
什么时候用 EXISTS 不用 IN?我自己的判断标准是:子查询返回的集合很大,或者集合字段可能为NULL时,用 EXISTS 更稳更快:
SELECT t.* FROM APPUSER.T_EMPLOYEE t WHERE EXISTS (SELECT 1 FROM APPUSER.T_DEPT d WHERE d.DEPT_ID = t.DEPT_ID AND d.STATUS = 1);因为 IN 遇到子查询返回NULL时会得到"查不到数据"的诡异结果,EXISTS 是逐行关联判断,不会踩这种NULL陷阱。
5.4 慢SQL与执行计划的几条实用SQL
说完查询技巧,顺手补几条排查慢SQL的实用语句。达梦的性能视图和Oracle长得像,定位慢SQL时我一般按这个顺序查:
-- 查看最近执行过的SQL文本和耗时 SELECT SQL_TEXT, ELAPSED_TIME, EXEC_TIME FROM V$SQL_HISTORY ORDER BY ELAPSED_TIME DESC;拿到慢SQL之后,用 EXPLAIN 看执行计划:
EXPLAIN SELECT * FROM APPUSER.T_EMPLOYEE WHERE EMP_NO = 'E1001';执行计划里重点看有没有全表扫描、有没有走索引,以及操作顺序是否合理。出现全表扫描时,第一步永远是评估索引是否建对,而不是急着改SQL。比如 EMP_NO 明明建了唯一索引但计划还是全表扫,多半是写了函数或隐式转换导致索引失效,需要先处理SQL写法。
6. 从Oracle/MySQL迁移到达梦后,SQL适配的高频踩坑点
6.1 Oracle迁移过来的主要适配点
Oracle项目迁达梦,整体最顺,但有三类坑要提前打预防针。
第一是空字符串。Oracle里 '' 等价于NULL,达梦默认兼容Oracle行为。听起来没问题,但如果你原来代码里有用 '' 判断"有值",迁到达梦后行为可能不一致。实际项目里我见过存储过程里 IF (v_str = '') 这种条件,在Oracle里永远为FALSE,因为''就是NULL,NULL比较结果为UNKNOWN。这类隐藏逻辑必须靠代码审查扫出来。
第二是包和内置函数。UTL_FILE、DBMS_LINK这类Oracle自带包,达梦虽然有类似实现,但函数名和参数不一定完全一致。我建议在迁移批次里单独列一个"包函数对照清单",把业务代码里用到的Oracle内置包全部过一遍,提前替换成达梦的等价调用。
第三是序列和游标。序列的 NEXTVAL/CURRVAL 基本兼容,但存储过程里游标的循环写法、BULK COLLECT 这些批量操作,达梦的支持程度需要逐个验证,不能因为编译通过就以为行为一致。
6.2 MySQL迁移过来的主要适配点
MySQL迁达梦,改动量要比Oracle大不少。最容易踩的坑按影响程度排:
- 反引号:MySQL里的反引号包标识符,达梦不支持,要改成双引号或去掉;
- 自增列:AUTO_INCREMENT 改为 IDENTITY(1,1);
- 函数:IFNULL 建议改成 NVL,IF(expr, v1, v2) 建议改成 DECODE 或 CASE WHEN;
- 表属性:ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 这类建表属性必须去掉,达梦建表语句里不认这些;
- 时间类型:TIMESTAMP 映射到达梦时可以直接用 TIMESTAMP 或 DATE,注意默认值 CURRENT_TIMESTAMP 的写法;
- LIMIT分页:MySQL的 LIMIT 10, 20 这种逗号写法达梦不认,要改成 LIMIT 20 OFFSET 10 或 OFFSET/FETCH。
一个典型的改造后建表语句长这样:
CREATE TABLE APPUSER.T_ORDER ( ORDER_ID BIGINT IDENTITY(1,1), ORDER_NO VARCHAR(32) NOT NULL, CUST_ID INT, CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (ORDER_ID) );6.3 用DTS和脚本导入的实际经验
数据迁移阶段,我一般先用达梦自带的 DM 数据迁移工具(DTS)做整体数据迁移。它的图形化向导可以选源数据库类型,Oracle、MySQL、SQL Server都能识别。配置好源库连接和目标库连接后,选择要迁移的模式和表,DTS会生成迁移任务并自动做类型映射。
常见的坑有三个:
- 大表迁移前先确认目标表空间的剩余空间,DTS不会自动帮你扩容;
- 迁移过程中如果报"精度丢失",多半是 NUMBER 长度映射问题,需要在映射规则里手工调整;
- 迁移完成后一定要抽样核对数据量,尤其是 CLOB/TEXT 字段,容易在边界条件下被截断。
如果是小项目的SQL脚本导入,我更习惯用 disql 命令行执行。进入 disql 后:
disql SYSDBA/SYSDBA_PWD@localhost:5236 SQL> START /backup/init_tables.sql; SQL> START /backup/init_data.sql;脚本里的SQL要注意编码。我吃过一次亏:Windows下导出的SQL文件是GBK,拿到Linux上的达梦里执行,中文注释全部乱码,后面改成UTF-8再导入才正常。
关于Windows下安装达梦、Linux命令行安装达梦,这些属于环境搭建的范畴,本文不再展开。但无论哪种安装方式,装完第一步永远是确认端口5236通、V$VERSION能查出版本号,再进行后面的SQL操作。
我自己在实际项目里的体会是,达梦的SQL兼容性在国产数据库里已经算做得相当不错了,Oracle用户上手尤其快。但数据库迁移这件事,最怕的就是"想当然"三个字。凡是文档没写明白的行为,都建议先开个测试库跑一遍看结果,把差异记录成清单,再推进正式割接。这套常用SQL总结覆盖了我日常工作中90%以上的场景,剩下的10%,等遇到再回来补充。