ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

达梦DM8数据类型与运算符详解:从兼容模式到SQL实战踩坑指南

达梦DM8数据类型与运算符详解:从兼容模式到SQL实战踩坑指南 数据库技术基础做到第9篇终于到了达梦数据库 DM8的数据类型与运算符环节。这一步跨过去基本就能自己建表、写查询、调存储过程了。如果你是刚从 MySQL 或 Oracle 转过来的这篇文章尤其值得看完——达梦在数据类型和运算符上兼有二者的影子但细节处差异不少很多线上问题恰恰出在这些细节上比如空字符串与 NULL 的关系、整数除法的返回类型、隐式转换的时机等。这篇文章会按“整体类型版图 → 常用类型逐一拆解 → 运算符体系 → 混用实战 → 排错链路”的顺序展开每一段都会给出具体示例和我在实际项目中踩过的坑。适合刚接触达梦的开发者、DBA以及正在做国产化迁移的团队参考。1. 达梦DM8数据类型全景先把地图铺开再往里走在学习具体类型之前先建立一张“地图”很重要。达梦 DM8 的数据类型整体上分层非常清晰和 Oracle 的对应关系最紧密同时因为要兼容 MySQL 生态也保留了 DATETIME、TIME、TINYINT 这类类型。我把常用类型分成五大类数值类型、字符类型、日期时间类型、大对象类型、二进制类型。分类常见类型对应Oracle类型对应MySQL类型典型用途数值INT、BIGINT、SMALLINT、TINYINTNUMBERINT、BIGINT计数、ID、状态码数值NUMBER(p,s)、DECIMAL(p,s)NUMBERDECIMAL金额、精确小数数值REAL、DOUBLE、FLOATBINARY_FLOAT/BINARY_DOUBLEFLOAT、DOUBLE科学计算、百分比字符CHAR、VARCHAR、VARCHAR2VARCHAR2CHAR、VARCHAR名称、编码、描述大文本TEXT、CLOBCLOBTEXT、LONGTEXT文章、JSON、日志日期DATE、TIME、TIMESTAMP、DATETIMEDATE、TIMESTAMPDATETIME、TIMESTAMP业务时间、记录时间二进制BLOB、BINARY、VARBINARYBLOB、RAWBLOB、BINARY图片、文件、加密数据特殊BIT、BOOLEANNUMBER(1)BOOLEAN、TINYINT(1)开关、标记一个很关键的点达梦实例在初始化时可以选择兼容模式。兼容 Oracle 时很多行为和 Oracle 趋同比如空字符串与 NULL 的关系、DUAL 表的使用兼容 MySQL 时则更贴近 MySQL 的习惯。这意味着同样的 SQL 在两种模式下执行结果可能不同。我曾经在一个项目里见过测试库和正式库用了不同兼容模式导致一段字符串拼接的存储过程表现不一致排查了很久才发现根因在初始化参数上。所以第一步先确认你的达梦实例到底是什么兼容模式再学类型和运算符。查询方式很简单disql 里执行SELECT * FROM V$PARAMETER WHERE NAME LIKE %COMPATIBLE%;或者在管理工具里查看初始化参数中的兼容模式配置。常见选项是 0 表示 Oracle 兼容、1 表示 MySQL 兼容。如果你的项目还在规划阶段我的建议是团队原来多用 Oracle 就选 Oracle 兼容模式原来多用 MySQL 就选 MySQL 兼容模式并保证所有环境统一。不要混着用否则数据类型和运算符的语义会漂移。2. 高频数据类型逐个过选型依据与踩坑点地图铺开后我们来逐个看那些真正高频的类型。这些类型是建表的主体也是你写 SQL 时打交道最多的对象。我会从“怎么选”“怎么写”“有什么坑”三个角度展开。2.1 数值类型ID、计数与金额的选型数值类型是最容易让人放松警惕的因为名字大家都认识。INT 和 BIGINT 分别对应 4 字节和 8 字节整数范围分别是正负 21 亿和正负 9 百亿亿。业务主键、自增列、订单号这类字段长期看选 BIGINT 更稳妥。TINYINT 在达梦中占 1 字节用来存 0/1 状态码很合适能省点空间但别用它存超过 255 的值。真正容易出问题的是 NUMBER 和 DECIMAL。这两个在达梦里本质上都是带精度和标度的定点数写法是 NUMBER(p,s)p 表示总位数s 表示小数位数。比如金额字段定义为 NUMBER(12,2)意思是整数部分最多 10 位、小数 2 位。有人嫌麻烦直接写 NUMBER不指定精度这在 Oracle 里有隐患但在达梦中默认支持的精度范围很大。注意如果小数位数固定一定要显式定义避免浮点误差影响金额计算。金额运算用 NUMBER 和 DECIMAL不要用 REAL 或 DOUBLE。REAL 和 DOUBLE 是浮点数适合统计报表中的比率、均值这类允许小误差的场景。浮点在二进制下无法精确表示十进制小数这是计算机通用问题不只在达梦里有。0.1 加 0.2 不等于 0.3 的现象如果你做过 JavaScript 开发应该不陌生。因此涉及钱的字段一律用 NUMBER/ DECIMAL。2.2 字符类型VARCHAR2、CHAR 与空字符串的隐形陷阱达梦的字符类型里最常用的是 VARCHAR 和 VARCHAR2。在达梦中 VARCHAR2 基本可以看作 VARCHAR 的别名二者都可以存储变长字符串按字节和字符的换算关系由字符集决定。CHAR 是定长类型插入“abc”时实际存储为“abc”加空格补齐。定长类型的优点是存储和比较更规整缺点是浪费空间而且容易在查询时产生“尾随空格”的错觉。接下来是重点中的重点在 Oracle 兼容模式下空字符串 会被自动转换为 NULL。这一点和 MySQL 完全不同。MySQL 里 和 NULL 是两个概念而在达梦兼容 Oracle 时你往 VARCHAR2 字段插入 查出来是 NULL。这个坑我见得太多CREATE TABLE T_USER( ID INT, NAME VARCHAR2(50) ); INSERT INTO T_USER VALUES(1, ); SELECT ID, NVL(NAME, IS_NULL) AS NAME FROM T_USER;结果 NAME 列返回 IS_NULL代表插入的确实成了 NULL。如果你的应用代码里有“判断字符串是否为空”的逻辑比如if name 在兼容 Oracle 模式下很可能失效因为读到的是 NULL。解决方案是应用层统一用框架的空判断或者 SQL 里写NVL(NAME,) 。在兼容 MySQL 的模式下 就是空字符串不会被转成 NULL这也是各兼容模式行为差异的典型代表。2.3 日期时间类型DATE、TIMESTAMP 和日期函数达梦的日期时间类型包括 DATE、TIME、TIMESTAMP、DATETIME。DATE 在达梦里既包含日期也包含时间粒度到秒TIMESTAMP 比 DATE 精度更高支持小数秒适合记录操作时间DATETIME 则更接近 MySQL 语义。默认情况下建表存“创建时间”用 TIMESTAMP 或 DATETIME 都可以但要注意应用层传来的时间格式。日期时间类型与运算符结合时有几个常见操作SELECT SYSDATE FROM DUAL; SELECT CURRENT_TIMESTAMP; SELECT DATE 2025-01-01 1 FROM DUAL; SELECT TIMESTAMP 2025-01-01 12:00:00 - TIMESTAMP 2025-01-01 10:00:00 FROM DUAL;这里值得提醒的是DATE 数字的含义。在 Oracle 兼容模式下日期加 1 表示加一天这个行为达梦也支持。但如果你写的是 TIMESTAMP 类型同样加 1 表示的是一天吗实际测试中TIMESTAMP 加整数同样按天计算但没有 DATE 那么直观容易出现歧义。更稳妥的写法是使用明确的间隔函数例如SYSDATE INTERVAL 1 DAY无论是 DATE 还是 TIMESTAMP 都不会产生歧义。日期格式化是另一个高频问题。很多从 MySQL 过来的人习惯用 STR_TO_DATE 和 DATE_FORMAT在达梦里对应的是 TO_DATE 和 TO_CHARSELECT TO_DATE(2025-06-01 12:30:00, YYYY-MM-DD HH24:MI:SS); SELECT TO_CHAR(SYSDATE, YYYY-MM-DD HH24:MI:SS);如果 TO_DATE 报错“无效的日期格式”先看第二个参数写对没有。月份用 MM分钟用 MI24 小时制必须加 HH24不能用 HH因为 HH 是 12 小时制容易出现上午下午混乱。2.4 大对象与二进制BLOB/CLOB 的正确打开方式业务中遇到大文本、文件内容、图片等就要用到 CLOB 和 BLOB。CLOB 存字符大对象比如超长 JSON、日志全文BLOB 存二进制大对象比如图片字节流、加密串。TEXT 类型在达梦里也支持可以看作是更轻量的字符大对象。使用大对象类型有两个建议。第一表和索引设计时尽量避免对大对象字段做排序、分组和 DISTINCT这些操作会把大对象放进临时表性能开销很大。第二查询时不要默认 SELECT *否则可能把几十 MB 的文本拉到客户端。正确做法是SELECT ID, SUBSTR(CONTENT, 1, 200) AS CONTENT_PREVIEW FROM T_ARTICLE WHERE ID 100;如果需要在应用层完整读取建议分批次读取用 DBMS_LOB.SUBSTR 这类函数截取片段。BLOB 在达梦中写入时可以使用参数化方式由驱动处理避免在 SQL 里直接拼接二进制字面量。3. 运算符体系拆解比较、逻辑、连接与集合运算的真实行为类型定义好了SQL 的威力靠运算符体现。达梦的运算符体系覆盖算术、比较、逻辑、字符串连接、集合运算、模式匹配等。这一节重点讲那些在达梦里有特殊行为、以及在不同兼容模式下表现不一样的部分。3.1 算术运算符除法和取模的结果容易和直觉不符算术运算符是 、-、*、/还有一个取模运算 % 或者 MOD 函数。加减乘大家都没问题关键是除法。在达梦兼容 Oracle 的模式下整数除以整数默认返回 NUMBER 类型结果可能带小数但在某些兼容模式或设置了特定参数时整数除法可能向下取整。为了不让结果依赖环境最好写查询时就明确预期SELECT 7 / 2 FROM DUAL; -- 结果可能是 3.5 SELECT CAST(7 AS DOUBLE) / 2 FROM DUAL;取模运算在达梦中有两种写法符号 % 和函数 MOD。7 % 2和MOD(7, 2)结果都是 1。对于负数取模不同数据库的符号约定不一样写业务逻辑前先测试预期的正负号。曾经有个报表需求要按月份取模分桶开发人员在 MySQL 上写好的取模逻辑迁到达梦后结果不对排查半天发现是负数取模的符号差异。3.2 比较运算符NULL 参与比较永远返回 UNKNOWN比较运算符包括 、、!、、、、。在达梦里这些运算符的语义是标准 SQL 的三值逻辑除了 TRUE、FALSE还有一个 UNKNOWN。也就是说任何值与 NULL 做比较结果都不是 TRUE也不是 FALSE而是 UNKNOWN。这意味着SELECT * FROM T_USER WHERE AGE NULL; SELECT * FROM T_USER WHERE AGE NULL;这两条 SQL 一条都查不出数据。正确写法必须用 IS NULL 或 IS NOT NULLSELECT * FROM T_USER WHERE AGE IS NULL;还有一种常见坑NOT IN 子查询中如果子查询结果包含 NULL整个查询结果为空。因为WHERE AGE NOT IN (SELECT AGE FROM T_OTHER)的语义是“AGE 不等于子查询中每一个值且不为 NULL”一旦子查询里有 NULL任何值都无法满足条件。解决方法是用 NOT EXISTS 代替 NOT INSELECT * FROM T_USER U WHERE NOT EXISTS ( SELECT 1 FROM T_OTHER O WHERE O.AGE U.AGE );3.3 逻辑运算符AND、OR、NOT 的短路与优先级逻辑运算符 AND、OR、NOT 用于拼接多个条件。先说优先级NOT 最高AND 次之OR 最低。所以WHERE A OR B AND C实际是WHERE A OR (B AND C)。如果我的真实意图是“(A OR B) AND C”必须显式加括号。这个规则几乎所有数据库通用但踩坑的人不在少数。达梦在执行逻辑运算时也存在短路行为。比如WHERE 11 OR 1/0 0因为前面条件已经为真后面的 1/0 根本不会执行所以不会报除零错误。反过来WHERE 10 AND 1/0 0同样因为前面为假而短路也不报错。理解短路有助于写出高效的过滤条件把成本低、选择性高的条件放前面。3.4 字符串连接|| 与 CONCAT 的兼容性字符串连接运算符在达梦里是||这在 Oracle 兼容模式下非常自然SELECT ABC || DEF FROM DUAL; -- 结果 ABCDEF达梦同时支持 CONCAT 函数。区别在于||可以一次连接多个字符串而 CONCAT 通常只接受两个参数。如果你需要连接三个字段写CONCAT(CONCAT(A, B), C)或者干脆用A || B || C。这里要特别提醒 NULL 的传播字符串连接中只要有一个操作数是 NULL结果就是 NULL除非你想把 NULL 当空串处理。例如SELECT 前缀 || NULL FROM DUAL;结果不是“前缀”而是 NULL。处理时可以用 NVL 函数把 NULL 转成空串SELECT 前缀 || NVL(NAME, ) FROM T_USER;3.5 集合运算与特殊运算符UNION、INTERSECT、MINUS集合运算符包括 UNION、UNION ALL、INTERSECT、MINUS。UNION 会去重UNION ALL 不去重。从性能角度能确定无重复数据时优先用 UNION ALL因为去重需要排序或哈希耗资源。INTERSECT 取交集MINUS 取差集。这些运算符在达梦中与 Oracle 语义基本一致使用时要求两侧查询的列数和数据类型匹配。另外达梦支持 ROWNUM、TOP、LIMIT 这几种限制行数的方式具体可以用哪一种取决于使用场景和兼容模式。在 Oracle 兼容模式下用 ROWNUM 和 TOP 都很自然而在 MySQL 兼容模式下 LIMIT 更顺手。如果想取前 10 行SELECT * FROM T_USER WHERE ROWNUM 10; SELECT TOP 10 * FROM T_USER; SELECT * FROM T_USER LIMIT 10;这三种写法在达梦中都能接受但团队规范最好统一一种避免代码库里出现三种风格。4. 类型与运算符混用的实战问题隐式转换、空值与兼容模式单独看类型和运算符都简单一旦混用就会暴露真实世界的复杂度。这一节整理三类高频混用问题。4.1 隐式转换方便的背后是性能与正确性的双重代价达梦和大多数数据库一样支持类型之间的隐式转换。例如 VARCHAR 类型的字段和数值常量比较时数据库会自动把字符串转成数值再比较。这是方便但隐患很大。SELECT * FROM T_ORDER WHERE ORDER_NO 123456;如果 ORDER_NO 是 VARCHAR 类型这条 SQL 会把 ORDER_NO 所有值都尝试转为数值型再比较。一旦某一行 ORDER_NO 里存在非数字字符比如 ABC123转换就会报错导致整条查询失败。更隐蔽的问题是隐式转换会让 ORDER_NO 列上的索引失效因为数据库无法对函数化后的列直接使用普通索引。我的建议是写 SQL 时比较、关联条件中的两侧类型尽量保持一致避免隐式转换。该用字符串常量的地方就补上引号该用数值的地方就不要传字符串。如果确实需要转换显式使用 CAST 或 CONVERTSELECT * FROM T_ORDER WHERE ORDER_NO CAST(123456 AS VARCHAR(20));在 WHERE 条件里写TO_CHAR(CREATE_TIME, YYYY-MM-DD) 2025-06-01这种写法也要注意它同样会让 CREATE_TIME 的索引失效。正确的思路是让列本身参与范围比较而不是把列包进函数SELECT * FROM T_ORDER WHERE CREATE_TIME TO_DATE(2025-06-01, YYYY-MM-DD) AND CREATE_TIME TO_DATE(2025-06-02, YYYY-MM-DD);4.2 空值与运算任何运算碰上 NULL结果基本还是 NULLNULL 与运算符之间有一条铁律NULL 参与算术、比较、连接运算结果基本都是 NULL 或 UNKNOWN。NULL 1 是 NULLNULL || abc 是 NULLNULL NULL 是 UNKNOWN。正因为这样聚合函数要注意。COUNT(*) 统计行数COUNT(列名) 统计非空值个数。SUM 对全部为 NULL 的列返回 NULL不是 0。业务上如果希望结果是 0需要 NVL 包裹SELECT NVL(SUM(AMOUNT), 0) AS TOTAL_AMOUNT FROM T_ORDER WHERE STATUS PAID;还有一个容易忽略的地方GROUP BY 分组时NULL 值会被归为一组。ORDER BY 排序时NULL 默认排在前面还是后面取决于兼容模式和排序参数。如果对 NULL 排序位置有要求用 NULLS FIRST 或 NULLS LAST达梦在 Oracle 兼容模式下支持这个语法。4.3 兼容模式对类型运算的实际影响一开始提到达梦支持不同兼容模式这里展开讲它对类型运算的具体影响面。同一套建表 SQL在两种模式下可能有几类显著差异空字符串是否视为 NULLOracle 兼容为是MySQL 兼容为否整数除法的返回精度和取整规则差异日期默认格式差异影响 TO_DATE 和 TO_CHAR 的格式解析标识符是否区分大小写、是否支持反引号举个例子你在 MySQL 兼容模式下建表写的反引号标识符user到 Oracle 兼容模式可能语法报错。反过来Oracle 兼容模式下常用的 DUAL 表在 MySQL 兼容模式下也可以使用但如果你习惯了SELECT 1不带 FROM 的写法达梦在某些模式下的容忍度也不一样。我的建议是项目启动会就把兼容模式定为技术基线写进开发规范里。不要指望同一套 SQL 在两个模式下都能完美运行。开发环境、测试环境、生产环境必须配置一致否则就会出现测试通过、生产报错的经典事故。5. 典型报错排查链路三个真实场景的定位过程最后分享几个我在实际项目中整理出来的排查思路。遇到类型或运算符相关的报错不要慌按链路一步步定位大多数问题都能快速收敛。5.1 场景一字符串转数值报错报错信息大概是“数据类型转换错误”或“无效的数值”。原因是某列存储的字符串中包含非数字字符而 SQL 中发生了隐式转换。排查步骤找到报错 SQL定位发生转换的列。用正则函数排查该列的异常数据达梦支持 REGEXP_LIKESELECT * FROM T_ORDER WHERE REGEXP_LIKE(ORDER_NO, [^0-9]);如果查出了脏数据做数据清洗或调整查询字段的写法。修改 SQL使用显式 CAST避免数据库替你做决定。5.2 场景二比较运算结果和预期相反一个典型例子是字符类型的订单号或编号按字符串比较而不是数值比较。10 和 9 在字符串字典序下10 排在 9 前面所以WHERE ORDER_NO 9查不出来 10。遇到这类问题先确认列的真实类型。如果是 VARCHAR 存数字要么改表结构为数值类型要么在比较时显式转类型SELECT * FROM T_ORDER WHERE CAST(ORDER_NO AS INT) 9;5.3 场景三TO_DATE 格式报错与日期范围不准确TO_DATE 报错先检查格式串是否匹配实际字符串。常见的错误是把分钟写成 MM把 24 小时制漏掉 HH24。日期范围不准确的另一个原因是字符串与日期比较时发生了隐式转换比如SELECT * FROM T_ORDER WHERE CREATE_TIME 2025-06-01;这里 CREATE_TIME 是 TIMESTAMP 类型字符串 2025-06-01 会被隐式转成日期。如果转换的默认格式与字符串不匹配就会报错。更稳妥的写法是SELECT * FROM T_ORDER WHERE CREATE_TIME TO_DATE(2025-06-01, YYYY-MM-DD);5.4 建立自己的类型与运算符自查清单排查问题做多了我习惯把常见坑沉淀成一份自查清单分享给你参考建表时金额用 NUMBER/DECIMAL不用 REAL/DOUBLEVARCHAR 存数字的列统一加 CAST 再比较所有 NULL 判断用 IS NULL / IS NOT NULL不用 NULLNOT IN 子查询如果可能包含 NULL改用 NOT EXISTS日期函数必须写全格式串不依赖默认格式字符串连接前用 NVL 处理可能为 NULL 的字段类型转换一律显式写不靠数据库隐式转换逻辑表达式优先级不清楚时加括号把这份清单打印出来贴工位上比临时翻文档好用得多。最后再说一点个人体会我从 Oracle 迁到达梦后最大的感受是达梦并不是“某个数据库的简单复制”它有自己的运算符行为和兼容层设计。与其被动踩坑不如把官方文档《DM8 SQL 语言使用手册》里数据类型、运算符这两章从头过一遍同时建一个自己的测试库把文中这些 SQL 亲手跑一遍。SQL 这东西光看文档记不住跑一遍比看十遍都管用。等你把数据类型和运算符的边界都摸清了后面学存储过程、触发器、性能调优都会顺畅很多。
返回列表