ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

达梦数据库SQL函数避坑指南:兼容性差异与索引优化实战

达梦数据库SQL函数避坑指南:兼容性差异与索引优化实战 你可能也遇见过这种情况一段在Oracle里跑得好好的SQL原封不动搬到达梦DM上刚执行就报错或者是同一个TO_CHAR格式化函数两边输出的结果就是不一样。我早两年做数据库迁移适配的时候就常常是在这种“看着眼熟但用起来打架”的SQL函数上折腾一下午。后来得出一条很朴素的结论达梦虽然以SQL兼容性为卖点但它的函数体系更像一套方言你在Oracle或者MySQL上的经验能帮你入门却不能帮你避坑。这篇东西不是官方文档的复述而是从SQL函数这个入口切入达梦数据库的实际经验。我会把日常开发、运维中最容易踩的几类函数问题拆开讲兼容模式对函数行为的影响、字符串/日期/数值函数的高频差异、正则函数四件套、窗口函数和行转列拼接、不算常见但很顶用的管道化表函数以及函数和索引之间那层说不清道不明的关系。文末还有一个我被第三方调试器带偏一上午的排错复盘。适合正在做从其他数据库到达梦迁移的开发者、DBA也适合刚接触达梦、想在SQL函数上少走弯路的同学。1. 达梦函数背后的“方言体系”先看清你坐在哪个模式下1.1 兼容模式不同函数的“性格”就不同达梦数据库在初始化实例的时候通常可以通过参数配置选择不同的SQL兼容风格。最常见的选择是兼容Oracle语法这也是大多数从Oracle迁移过来的项目默认会选的模式但同时达梦也支持兼容MySQL等其他风格。函数层面的影响非常直接某个函数在这个实例里叫LISTAGG换个兼容模式下可能就要写GROUP_CONCAT空字符串和NULL是否等价也会因为模式不同而表现不同。这一点是很多函数报错的第一根源。我见过一个团队在排查“同一个SQL开发库能跑测试库报错”的问题最后发现两个实例的兼容模式不一致。所以如果你在群里问“达梦支不支持某个函数”别人回答“支持”先别急着信问一句对方的兼容模式是什么。实际操作层面可以在达梦自带的DM管理工具或者isql里查看当前实例的参数关注和兼容性相关的配置。我不建议为省事直接去改实例级参数因为这会影响整个实例里所有对象的行为如果只是个别项目需要优先考虑在客户端SQL层面做兼容写法或者用达梦提供的兼容视图、同义词机制去适配。1.2 空值、空串和大小写三个看着无关却能坑人的基础行为在Oracle兼容模式下空字符串在很多语义上和NULL是等价的。这意味着你写WHERE col 看起来是想找“空串记录”实际执行时会被当成WHERE col NULL处理结果一条都查不出来。这个行为在达梦的Oracle兼容模式下同样存在。习惯了MySQL或者SQL Server的人到这里会非常难受因为那边是真正的空串NULL是NULL两者分得很清楚。大小写则是另一个经典入口。达梦默认情况下不加双引号的对象名会统一转成大写存储也就是说你创建表时写create table emp它在数据字典里存的是EMP。函数名本身大小写不敏感但函数参数里的字符串字面量完全区分大小写。比如LOWER(ABC)返回abcUPPER(abc)返回ABC这些都是正常的真正容易翻车的是拿一个函数去处理对象名或者字段编码值忘了数据本身的大小写规则结果REGEXP_SUBSTR怎么匹配都匹配不上。这类基础行为不值一提但很多玄学问题层层剥开之后最后都落在这几个点上。所以我建议遇到达梦SQL函数相关的问题先花两分钟确认三件事当前实例兼容模式、目标字段的数据大小写、以及查询条件里用的是NULL还是空串。确认完这三件至少能排除一半的干扰项。2. 高频字符串、日期、数值函数迁移对照表里的那些“坑”从Oracle迁移到达梦的大多数SQL其实没有用到什么高深函数反而是SUBSTR、TO_CHAR、TRUNC这些天天在用的函数最出问题。不是达梦不支持而是细节行为有差异尤其是同名函数在不同参数类型下走的是完全不同的逻辑。2.1 字符串函数SUBSTR、INSTR、REPLACE这几个高频词的细节先说SUBSTR。达梦里SUBSTR是按字符位置截取不是按字节所以SUBSTR(达梦数据库, 2, 3)的结果是“梦数据”。如果你要用字节截取得用SUBSTRB在UTF-8字符集下一个中文字符通常占3个字节这会导致SUBSTRB的第二个参数和第三个参数都要按字节数来算一旦边界没算准很容易把中文截成半个字输出乱码。日常开发建议优先用SUBSTR按字符处理只有实在需要控制存储字节数时才用SUBSTRB。INSTR是另一个容易把参数顺序记反的函数。INSTR(source, search, position, occurrence)第3个参数是“从哪个位置开始找”第4个参数是“找第几次出现”。比如INSTR(abcabc, a, 1, 2)返回4因为第二次出现a是在第4位。很多人只记得前两个参数后面两个默认值1和1一旦需求变成“从第3位开始找”或者“找第二次出现”就很容易把位置参数写成搜索串。REPLACE的坑在于第三个参数。REPLACE(ABC, B, )的结果是AC因为空串相当于“删掉匹配字符”但如果第三个参数传的是NULL结果会保持原串不变因为NULL在语义上表示“不执行替换操作”。这个行为和直觉有偏差尤其在动态拼接SQL、把参数从外部传进来的时候空串和NULL很容易混淆替换结果就会完全不同。2.2 日期与数值函数同名函数的不同行为是重灾区TO_CHAR、TO_DATE、TRUNC这三个函数几乎每天都在用但细节坑最多。TO_CHAR格式化日期时最容易被忽略的是小时制的问题。如果你只写TO_CHAR(sysdate, YYYY-MM-DD HH:MI:SS)这里的HH是12小时制下午两点会显示成02而不是14同时也没有AM/PM标识看起来就像凌晨两点。正确写法是HH24:MI:SS24小时制。另一个容易踩的是TO_CHAR(number, format)和TO_CHAR(date, format)是两个“同名但参数含义完全不同”的用法前者处理数字格式化后者处理日期格式化混用时容易想当然。TRUNC也是一个典型的重灾区。TRUNC(SYSDATE)返回当天零点TRUNC(3.14159, 2)返回3.14TRUNC(SYSDATE, MM)返回当月1号零点。同一个函数名参数类型不同、第二参数含义不同结果天差地别。编译期不会报错但结果可能离预期很远。日期运算还有几个高频函数值得留意。ADD_MONTHS是月末处理的神器ADD_MONTHS(TO_DATE(2024-01-31, YYYY-MM-DD), 1)在大多数情况下会返回2024-02-29而不是简单粗暴地加30天。MONTHS_BETWEEN返回的是带小数的月份差如果你只需要整数个月建议套一层ROUND。LAST_DAY(date)返回当月最后一天它和TRUNC配合可以快速算出月初第一天也就是LAST_DAY(SYSDATE) - EXTRACT(DAY FROM SYSDATE) 1这种写法在做月度报表时很常见。数值函数的坑相对少但NVL要提一下。NVL(commission, 0)要求两个参数类型尽量一致如果commission是VARCHAR2而0是数字字面量达梦在隐式转换时可能会报类型不匹配或者按某种隐式规则转换出意想不到的结果。DECODE在达梦里是支持的但可读性不如CASE WHEN新写的SQL我通常建议优先用CASE。这里我单独放一张自己一直在用的迁移对照表供你直接参考函数Oracle习惯达梦注意点SUBSTR按字符截取与Oracle一致UTF-8下别用字节思维SUBSTRB按字节截取中文一个字符约3字节容易截出乱码INSTR4个参数第3参数是起始位置第4参数是第几次出现REPLACE3个参数第三参数为NULL时保持原串为时删除TRIM/LTRIM/RTRIM去字符第二个参数按“字符集合”处理不是按整个字符串匹配TO_CHAR格式掩码日期格式化用HH24数字格式化注意格式符TRUNC重载函数对日期和数值行为完全不同ADD_MONTHS月末加法1月31日加1个月会得到2月末不是3月2日MONTHS_BETWEEN返回小数取整数月份差记得ROUNDNVL/NVL2空值处理两个参数类型要尽量一致3. 正则表达式函数REGEXP家族在达梦里的打开方式正则函数在达梦里和Oracle的形态非常接近主要是四个REGEXP_LIKE、REGEXP_INSTR、REGEXP_SUBSTR、REGEXP_REPLACE。用途分别是判断是否匹配、返回匹配位置、提取匹配子串、替换匹配内容。3.1 四个核心正则函数的参数顺序值得背下来REGEXP_LIKE(source, pattern[, match_param])是最简单的判断函数常用于WHERE条件里的模糊校验例如判断字段是否为合法邮箱。REGEXP_INSTR(source, pattern[, position[, occurrence[, return_opt[, match_param]]]])返回的是匹配到的位置第三个参数表示从源字符串的第几个字符开始查找第四个表示第几次出现第五个return_opt如果是0返回起始位置如果是1返回结束位置的下一位。这个函数我平时用得不多但做日志解析、协议解析时会很有用。REGEXP_SUBSTR(source, pattern[, position[, occurrence[, match_param[, subexpr]]]])是提取函数它的第6个参数subexpr可以指定返回第几个括号捕获组。这里有个高频误区很多人以为第5个参数是捕获组实际上第5个是match_param第6个才是subexpr。我之前在这个参数上栽过一次后来干脆把函数签名的顺序抄下来贴在显示器旁边。REGEXP_REPLACE(source, pattern[, replace_string[, position[, occurrence[, match_param]]]])和普通REPLACE的区别是支持正则模式。清洗数据时一行REGEXP_REPLACE就能顶掉十行传统REPLACE嵌套。3.2 实际场景清洗手机号、提取金额、校验邮箱直接看三个案例都是我从实际项目里摘出来的写法。清洗手机号去掉所有非数字字符SELECT REGEXP_REPLACE(phone, [^0-9], ) AS clean_phone FROM user_info;这个需求场景通常是用户上传Excel时手机号里混入了空格、横杠或者中文传统REPLACE要嵌套很多层正则一行搞定。从订单备注里提取金额并且要拿到括号里的数值部分SELECT REGEXP_SUBSTR(remark, 金额[:]?([0-9](\\.[0-9])?), 1, 1, i, 1) AS amount FROM orders;SQL字符串里的双反斜杠是为了在字符串字面量里转义出正则的反斜杠这个细节特别容易漏。少写一个斜杠正则就变成了普通字符点号匹配逻辑完全改变。校验邮箱格式SELECT email FROM users WHERE REGEXP_LIKE(email, ^[A-Za-z0-9._%-][A-Za-z0-9.-]\\.[A-Za-z]{2,}$);括号里的表示至少一个点号要转义成\.末尾的$表示必须结束。还有一个容易被忽略的行为正则函数默认区分大小写。如果你要忽略大小写匹配记得传第5个参数i在REGEXP_LIKE里就是第3个参数match_param传i。我见过很多次“明明正则看起来没问题但就是匹配不到”最后都是大小写参数惹的祸。3.3 正则函数与性能正则函数写起来爽但性能代价不小。它是典型的CPU密集型计算尤其是在大表上做REGEXP_LIKE过滤时普通索引基本用不上只能全表扫描。如果业务上必须高频使用正则过滤我一般建议用函数索引配合或者把清洗结果落到单独的字段里在写入时计算好查询时直接过滤普通字段。4. 窗口函数与聚合拼接让SQL少绕很多弯的进阶用法窗口函数在达梦里的完整度比我预想的高从排名到累加、从LAG/LEAD到聚合开窗基本都支持。这类函数最大的价值是减少子查询和自连接尤其适合报表和统计类的SQL。4.1 排名函数和“分组取Top N”一个经典需求拆解三大排名函数经常被放在一起比较ROW_NUMBER()是连续编号相同排序列的几条记录会随机分到1、2、3RANK()遇到相同值会并列但下一个名次会跳跃两个第1名之后直接是第3名DENSE_RANK()也是并列但名次不跳跃两个第1名之后是第2名。最典型的“每个部门薪资前三”需求用ROW_NUMBER就能写SELECT emp_id, emp_name, dept_id, salary FROM ( SELECT emp_id, emp_name, dept_id, salary, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn FROM emp ) t WHERE rn 3;这里有个新手必踩的坑窗口函数生成的别名不能直接写在WHERE里必须包一层子查询。如果你尝试在外面直接WHERE rn 3数据库会报无法解析该列因为WHERE子句的执行顺序在SELECT别名之前。窗口函数不只是排名。SUM(amount) OVER (PARTITION BY user_id ORDER BY pay_date)可以生成累计消费金额这就是移动累计的实现方式在很多跟“余额”“进度”“趋势”相关的报表里非常常见。LAG和LEAD可以从当前行拿到上一行或下一行的值做环比、同比时非常方便SELECT pay_date, amount, LAG(amount, 1) OVER (ORDER BY pay_date) AS prev_amount, amount - LAG(amount, 1) OVER (ORDER BY pay_date) AS diff_amount FROM daily_pay;LAG(amount, 1)表示取按pay_date排序后上一行的amount没有上一行时默认是NULL。4.2 行转列拼接LISTAGG、WM_CONCAT和KEEP DENSE_RANK把多行数据拼到一行是报表开发里出现频率极高的需求。达梦支持LISTAGG写法跟Oracle一致SELECT dept_id, LISTAGG(emp_name, ,) WITHIN GROUP (ORDER BY salary DESC) AS emp_list FROM emp GROUP BY dept_id;WITHIN GROUP里的ORDER BY控制的是拼接顺序这个排序列只影响拼接结果不影响分组。另一个常见函数是WM_CONCAT很多人喜欢用因为它短SELECT dept_id, WM_CONCAT(DISTINCT emp_name) FROM emp GROUP BY dept_id;但WM_CONCAT有两个问题一是拼接顺序不保证今天出来一个顺序明天可能是另一个二是DISTINCT要在函数参数里显式写不写的话不会自动去重。所以有明确顺序要求时我优先用LISTAGG。LISTAGG还有一个容易被忽略的限制拼接结果有长度上限。如果拼出来的字符串超过VARCHAR类型的最大长度会直接报字符串超长的错误。处理办法一般是先预估单组分组的行数和平均长度万一超长要么缩小拼接内容要么把结果往CLOB字段里放或者干脆调整分组维度。再说一个不算大众但非常实用的KEEP DENSE_RANK FIRST语法。它的场景是“分组内按A列排序取B列的最值”比如查每个部门第一个入职的人的薪资SELECT dept_id, MAX(salary) KEEP (DENSE_RANK FIRST ORDER BY hire_date) AS first_hire_salary FROM emp GROUP BY dept_id;这种需求用传统写法要两步先算每个人入职排名再筛分组第一用KEEP语法一步就能出结果达梦对它的支持也一样。窗口函数对执行计划的影响也要提一下。窗口函数本身不直接导致索引失效但如果OVER里的PARTITION BY和ORDER BY列缺少合适索引排序和分组就会落到临时表空间数据量大时性能会非常难看。建议给高频窗口查询建立“排序列分区列”的组合索引。5. PIPE管道化表函数一种“边算边出”的自定义函数管道化表函数在达梦里属于不太常见但非常好用的功能。触发这个话题的是我最近看达梦相关技术社区时好几个人在问PIPE函数是什么、怎么用。5.1 从水龙头说起普通表函数和管道函数的区别普通表函数返回一个完整的集合就像你要喝水它先把你一个月要喝的水全部接到一个大桶里再递给你。管道化表函数则不一样它像水龙头你拧一下出一滴水数据库取走一行函数再继续算下一行整个结果是流式输出的。这个差异在数据量大的时候非常明显。管道函数不需要在内存里构建完整结果集也不需要在函数返回前把全部数据算完对内存占用和首行返回时间都更友好。典型场景包括把逗号分隔的字符串拆成多行、生成连续日期序列、把外部文件解析成结构化行、做流式转换等。5.2 完整可跑的示例拆字符串和生成日期序列我先把最常用的字符串拆分函数完整写出来。这个函数在报表开发里几乎天天用比如前端传过来一个“北京,上海,广州,深圳”的筛选条件后端可以直接用它拆成四行去关联。第一步定义行类型和集合类型CREATE OR REPLACE TYPE str_row_t AS OBJECT(str_val VARCHAR2(4000)); CREATE OR REPLACE TYPE str_tab_t AS TABLE OF str_row_t;第二步定义管道函数CREATE OR REPLACE FUNCTION split_str( p_str VARCHAR2, p_sep VARCHAR2 DEFAULT , ) RETURN str_tab_t PIPELINED AS v_src VARCHAR2(4000) : p_str; v_pos NUMBER; BEGIN LOOP v_pos : INSTR(v_src, p_sep); IF v_pos 0 THEN PIPE ROW(str_row_t(TRIM(SUBSTR(v_src, 1, v_pos - 1)))); v_src : SUBSTR(v_src, v_pos LENGTH(p_sep)); ELSE PIPE ROW(str_row_t(TRIM(v_src))); EXIT; END IF; END LOOP; RETURN; END;第三步查询测试SELECT * FROM TABLE(split_str(北京,上海,广州,深圳));查出来的结果就是四行每行一个城市名。这个示例里我用到了INSTR和SUBSTR的组合来循环切分字符串。每PIPE ROW一次函数就交出一行调用方可以立刻消费。注意这只是演示逻辑实际用于生产时建议加上空值判断防止p_str为NULL时死循环或者多出一行空串。再给一个生成连续日期的管道函数做补数或者生成日报日期维度时很管用CREATE OR REPLACE FUNCTION gen_dates(p_start DATE, p_days NUMBER) RETURN str_tab_t PIPELINED AS v_cur DATE : p_start; BEGIN FOR i IN 1..p_days LOOP PIPE ROW(str_row_t(TO_CHAR(v_cur, YYYY-MM-DD))); v_cur : v_cur 1; END LOOP; RETURN; END;查询SELECT * FROM TABLE(gen_dates(TO_DATE(2024-07-01, YYYY-MM-DD), 7));会得到连续7天的日期字符串。这里v_cur : v_cur 1利用了达梦日期加整数的运算规则加1就是加一天和Oracle一致。5.3 管道函数使用中的三个注意点第一返回的集合类型必须提前定义好行类型和集合类型要匹配PIPE ROW传出的对象要和行类型一一对应否则编译时不容易暴露执行时才报类型不匹配。第二管道函数在查询里必须用TABLE()包裹直接SELECT fn()是不行的这一点和Oracle一致。第三要注意优化器对管道函数的行数估算通常不准。它不知道你最终会返回多少行默认值可能很小如果和外表做连接可能选择嵌套循环而不是哈希连接导致执行计划不理想。数据量大时我会先把管道函数结果灌到临时表再参与后续关联性能反而更稳定。6. 函数与索引的爱恨情仇从执行计划里的CLUSTERBTR说起最近看到有人在搜“达梦 索引 clusterbtr”这个词在达梦执行计划里很典型。很多DBA第一次在计划里看到CLUSTERBTR都会楞一下以为是什么特殊算法其实就是和聚簇B树索引相关的一种扫描方式。6.1 扫表方式和函数的关系在达梦的执行计划里能看到类似CSCN、CSEK、CLUSTERBTR这类扫描标识。简单理解CSCN是全表扫描CSEK这类是索引定位扫描CLUSTERBTR则是沿着聚簇B树的索引结构去扫描。看到CLUSTERBTR出现在计划里通常说明优化器认为这个索引路径是可行的、省事的而不是傻乎乎地全表扫。但问题在于如果查询条件里对索引列做了函数包装情况就变了。比如索引建立在create_time上但你的条件写成WHERE TO_CHAR(create_time, YYYY-MM-DD) 2024-07-01对create_time做TO_CHAR之后索引列本身被隐藏在一堆函数计算后面优化器没法直接拿它去和索引里的键值做范围匹配往往就会放弃CLUSTERBTR之类的索引路径改走全表扫描。我见过太多SQL慢就慢在这么一层函数包装上。不是达梦不智能而是函数处理后的结果已经无法和原始索引键对应起来这跟Oracle、MySQL遇到的情况本质一样。6.2 解法一函数索引如果业务确实需要按函数表达式查询那就把这个表达式建进索引里。达梦支持函数索引也就是在索引定义时直接写函数CREATE INDEX idx_emp_upper_name ON emp(UPPER(emp_name));之后查询写WHERE UPPER(emp_name) ZHANGSAN优化器就能识别这个函数表达式直接走索引。使用函数索引有几个前提条件。函数必须是确定性的也就是同样的输入永远得到同样的输出像SYSDATE、RAND这类函数不能用来建函数索引。此外查询里写函数表达式时必须和索引定义完全一致大小写、参数个数、空格都不能差否则优化器仍然认为你是两个不同的表达式。6.3 解法二SQL改写把函数从列上挪走函数索引虽然好用但不能无限堆。更优雅的做法是改写SQL让索引列保持原样。还是刚才那个TO_CHAR(create_time)的例子可以改成范围条件WHERE create_time TO_DATE(2024-07-01, YYYY-MM-DD) AND create_time TO_DATE(2024-07-02, YYYY-MM-DD)这样create_time没有经过任何函数处理天然就能用上索引而且语义完全一致7月1日零点到7月2日零点之间的所有时间点。这个改写的思路可以推广到很多场景。查询条件里看到LOWER(col)、UPPER(col)、TO_CHAR(col, YYYY)、SUBSTR(col, 1, 4)这类写法时先别急着建函数索引想想能不能把函数挪到常量一侧或者改成范围条件。能直接走普通索引比什么都强。6.4 排查SQL慢的时候怎么判断是函数惹的祸拿到一条慢SQL我一般按这个顺序排查看执行计划确认有没有全表扫描。看WHERE条件里的列有没有被函数包裹。注意有的函数是隐式的比如把字符串字段和数字字面量比较数据库可能做隐式类型转换实际上也是一种函数包装。确认索引列的实际类型和查询参数类型是否一致。如果确定是函数导致索引失效再决定改写还是建函数索引。这一步做完很多“加了索引但SQL还是慢”的谜案都能破。函数本身不是洪水猛兽但让函数出现在索引列上等于亲手给索引上了把锁这个意识一定要有。7. 一次“函数返回结果集”排错复盘别让第三方调试器带偏你最后分享一个我印象很深的排错过程。它表面上是一个SQL函数的问题实际上差点把整个数据库函数逻辑翻了个底朝天最后发现和数据库函数本身毫无关系。7.1 诡异报错PInvoke stack imbalance当时一个同事用某第三方可视化调试器去调试达梦的存储过程存储过程里调用了一个自定义函数这个函数的返回类型是嵌套表也就是类似上一章PIPE函数返回的集合类型。每次调试一跑到函数调用那一步调试器就弹一个PInvoke stack imbalance的报错大意是对某个函数调用时托管调用堆栈不平衡。第一反应当然是觉得自定义函数写崩了于是几个人对着函数定义逐行检查函数体明明只有一行SELECT怎么也看不出问题。后来有人提了一句“这个报错是调试器抛的不是数据库抛的。”一句话点醒了我。7.2 还原排查链路我把当时的排查步骤整理成了一条可复用的链路以后你在达梦里遇到函数相关诡异报错可以先按这个顺序走先剥离工具层。打开达梦自带的DM管理工具或者isql直接在数据库层面执行这个函数。比如SELECT * FROM TABLE(某个函数(...))如果数据库层面正常返回结果集说明SQL函数本身的逻辑、类型定义、权限都没问题。再隔离接口层。用一个最简单的JDBC或者ODBC示例代码只做“连接数据库、调用函数、打印结果”三件事。如果JDBC调用正常说明驱动和数据库的交互没问题如果同样报错那就是驱动版本和达梦版本之间的兼容性问题去换驱动版本而不是去改函数。最后才回到调试工具层。PInvoke stack imbalance这个报错的本质是托管代码在调用非托管原生接口时调用约定不匹配导致堆栈无法正确平衡。这在第三方调试器、可视化开发工具里经常出现和数据库函数的正确性没有直接关系。结论是我们那天遇到的函数一点问题没有就是调试器在翻译达梦原生接口时水土不服。换回数据库层面验证或者升级调试器版本后问题消失。7.3 平时怎么给达梦函数做自查经过这件事我养成了几个习惯。任何函数写完先在DM管理工具里用最简单的SQL验证一遍而不是直接扔进业务代码里跑这一步能过滤掉80%的低级错误。验证函数的返回类型和数据列类型是否匹配尤其是集合类型、嵌套表的元素类型类型对不上时创建可能成功调用时才报错非常隐蔽。定期用达梦的系统视图查一下函数状态如果对象状态是INVALID说明依赖的表结构变了或者编译失败需要重新编译。还有一个小细节函数命名和参数命名尽量规范别用保留字或者系统函数同名。我在有些项目里见过有人创建了一个名为PIPE的函数结果和系统关键字撞车调用时各种诡异后来改名就好了。这类排错复盘想表达的核心其实很简单遇到函数报错先分清问题出在哪一层是SQL函数本身是数据库驱动还是写函数的工具。分清层级之后排查就变成一道简单的二分题而不是在函数逻辑里瞎转圈。我个人还有一个很笨但很有效的习惯每接触一个达梦版本就先用一张用例表把常用函数跑一遍记录三条信息——兼容模式、输入样例、输出结果。后来不管是迁移SQL还是帮同事排查函数问题遇到拿不准的直接翻这张表比翻官方文档和搜索引擎快得多。达梦的SQL函数整体和Oracle很接近但细节差别就是会冷不丁冒出来坑你一下。你手里那份属于你自己的函数用例表才是真正靠得住的避坑指南。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进