前言
前段时间,公司要求要把某几个项目的数据库换成达梦数据库,说是为了国产化。我就挺无语的,三四年的项目了,现在说要换数据库。我一开始以为这个达梦数据库应该是和TIDB差不多的。
我之前做的好几个项目部署到测试服、正式服就是用的TIDB,因为它几乎完全兼容MySQL,我本地都甚至没有安装,打包到测试服有极少数不兼容的写法也是临时改的。
然后我去查了下资料,按照教程安装了达梦,并且使用它自带的迁移工具,将数据库迁移到了达梦;再将项目的pom、yml文件也改了之后,运行,好,正常启动了,于是点页面,我直接傻眼,全报错。点一个页面就报错。全是语法问题,无效的列名、无效的函数这些。
我直接:
好好好,这么玩是吧,我怒改SQL,然后发现不管是查询,还是新增、修改、删除,只要是原生SQL,绝大多数都要改。
这个时候就完美体现了mybatis-plus的好处了,用的它自带的增删改查,就完全不用改,除非是查询用到了QueryWrapper的apply方法构建了sql,sql里面有达梦不支持的写法或函数,才需要改。
那我没有办法,项目很多都是联表查询、子查询、分组统计,写了很多原生SQL;新增和修改也是,很多地方用到 on DUPLICATE key
,也全部要改掉。
所以这篇文章来说一下我改SQL的过程中,遇到的一些不兼容的写法,应该如何改正。
转移
首先要先去下载安装达梦数据库,安装完了后,可以使用那个工具自带的数据迁移工具,将MySQL迁移至达梦。
下载的时候,记得下载最新版,我自己下载的是2024.4月份的版本,新版对mysql的兼容性最好,能最小程度地改动sql。当然如果公司要求用某个旧版本,那就没办法了。我就是,我本地用的新版,其实没有花太多时间改,都是抽空改的,边改边研究,不知道达梦对应的写法或函数就问AI,花了不到两天时间就改完了,全部可以正常运行。但是!!!我打包发给别人部署的时候,全是报错,我人都傻了,晚上我人都在家躺着了,都被叫回去加班了😭后面在我的追问下,才告诉我正式服用的版本是2021.9月份的版本。。。问能不能升级,说不能,甲方要求要用这个版本。。。
迁移完之后,打开DM管理工具,新建连接,用户名密码默认是 SYSDBA,连接进来后,点开模式,就可以找到之前迁移的数据库了。(在达梦,模式就是对应MySQL的数据库)
不过好像说直接用迁移工具迁移会丢失数据,就是有些不兼容的地方。。。
还有个注意的地方,就是你迁移好了,把这个模式导出成 dmp文件,需要注意你这个模式有没有设置 模式拥有者
,如果设置了,那么在另一个地方给同样的模式导入的这份数据的时候,也需要给另一个地方的模式设置同样的模式拥有者
。
比如,模式名叫 DEMO
,你设置了它的拥有者 TEST
,之后导出这个模式,得到了一个dmp文件。然后你在另一台电脑上,想要导入这份dmp数据,就需要建一个同名模式 DEMO
,同样设置拥有者为 TEST
,这样再去导入那份dmp文件,才可以。(模式拥有者
可以在用户——>管理用户 这里右键,新建用户,这里的用户就可以设置成 模式拥有者
)
除了导出成dmp文件,也可以导出成sql文件,
将数据迁移到达梦后,就可以在项目中,开始改代码,换成达梦了。
改SQL
group by
在mysql中,使用了group by,假如要查询非分组的字段,除了用max()、min()之外,像如果是字符串类型,我一般会用 any_value(),达梦没有这个函数,我就直接改成用max()或min()了,这是最快的方法。
还有一个旧版本需要注意的问题:假如你的select中,需要对某个字段a进行计算或者其它处理,最后的结果被命名为了b,但是在表中并不存在b这个字段,然后对b进行 group by,代码如下:
# result在 sys_user 表中并不存在
SELECT DATE_FORMAT(create_time,'%Y-%m-%d') result
FROM sys_user
GROUP BY result
在旧版本达梦中(具体多旧不知道,反正2021.9这个版本是不支持的),这样的写法不支持,会报无效的列名 result。要想在改动最小的情况下正确执行这段代码,需要再包一层:
SELECT result
FROM(SELECT DATE_FORMAT(create_time,'%Y-%m-%d') resultFROM sys_user
) a
GROUP BY result
关于true和false
在旧版本中,直接使用 true 和 false 作为字面量在是不被识别的,因为它期望的是具体的数值或字符串值。
所以if(state > 1,true,false)
或者 CASE WHEN state > 1 THEN true ELSE false END
都会报错。需要将true、false换成具体的数值或字符串才可。
日期格式化、日期加减运算
MySQL中,日期格式化,一般是用 DATE_FORMAT 函数,然后在达梦中,对应 DATE_FORMAT 函数的用法是 TO_CHAR(date,'YYYY-MM-DD')
,格式化的format写法也和mysql的format写法不太一样,这个需要注意。
第二个就是日期时间处理计算,经常会用到的加减时间运算。
MySQL的用法:
# 减运算
DATE_SUB(date,INTERVAL expr unit)
# 加运算
DATE_ADD(date,INTERVAL expr unit)
达梦的用法:
# 减运算
date - INTERVAL expr unit
# 加运算
date + INTERVAL expr unit
多的请看:达梦技术文档-日期运算
on DUPLICATE key 的替换方案
MySQL中,on DUPLICATE key UPDATE
的使用还是挺频繁的,那么在达梦中应该怎么实现一样功能效果呢?
假设我是根据唯一约束索引 user_name 来判断新增或更新的(用主键id也可以)
MySQL中,我有下面一段SQL
<insert id="insertOrUpdateBatch"><foreach collection ="list" item="item" separator =";">INSERT INTO sys_user<trim prefix="(" suffix=")" suffixOverrides=","><if test="null != item.id and '' != item.id">id,</if><if test="null != item.userName and '' != item.userName">user_name,</if><if test="null != item.realName and '' != item.realName">real_name,</if><if test="null != item.deptId and '' != item.deptId">dept_id,</if><if test="null != item.roleId and '' != item.roleId">role_id,</if><if test="null != item.password and '' != item.password">password,</if></trim>VALUES<trim prefix="(" suffix=")" suffixOverrides=","><if test="null != item.id and '' != item.id">#{item.id},</if><if test="null != item.userName and '' != item.userName">#{item.userName},</if><if test="null != item.realName and '' != item.realName">#{item.realName},</if><if test="null != item.deptId and '' != item.deptId">#{item.deptId},</if><if test="null != item.roleId and '' != item.roleId">#{item.roleId},</if><if test="null != item.password and '' != item.password">#{item.password},</if></trim><!-- 根据唯一约束 user_name 来更新下面几个字段的值 -->on DUPLICATE key UPDATE<trim suffixOverrides=","><if test="null != item.realName and '' != item.realName">real_name = #{item.realName},</if><if test="null != item.deptId and '' != item.deptId">dept_id = #{item.deptId},</if><if test="null != item.roleId and '' != item.roleId">role_id = #{item.roleId},</if><if test="null != item.password and '' != item.password">password = #{item.password},</if></trim></foreach>
</insert>
或者是:
INSERT INTO sys_user (id,user_name,real_name ,dept_id ,role_id,password)
VALUES
<foreach collection ="list" item="item" separator =",">(#{item.id},#{item.userName},#{item.realName},#{item.deptI},#{item.roleId},#{item.password})
</foreach>
on DUPLICATE key UPDATE real_name = values(real_name),dept_id = values(dept_id),role_id = values(role_id),password = values(password)
那么在达梦中,就改成这样的写法:
MERGE INTO sys_user T1
USING (<foreach collection="list" item="item" index="index" separator="UNION ALL">SELECT #{item.id} id,#{item.userName} user_name,#{item.realName} real_name,#{item.deptId} dept_id,#{item.roleId} role_id,#{item.password} passwordFROM dual</foreach>
) T2 ON (T1.user_name = T2.user_name) <!-- 根据唯一约束 user_name 来更新 -->
# ) T2 ON (T1.id = T2.id) <!-- 这里就是根据主键 id 来更新 -->
WHEN NOT MATCHED THEN INSERT(id, user_name, real_name,dept_id,role_id,password)
VALUES (T2.id, T2.user_name, T2.real_name, T2.dept_id, T2.role_id, T2.password)
<!-- 更新这几个字段的值 -->
WHEN MATCHED THEN UPDATE SET T1.real_name = T2.real_name,T1.dept_id = T2.dept_id,T1.role_id = T2.role_id,T1.password = T2.password
FIND_IN_SET
FIND_IN_SET函数,也是经常使用的一个函数,我去找了下,在22年4季度的版本以后,达梦才支持这个函数的,之前的版本都不支持。
然后我去找了下有没有替代方案,发现无一例外都是叫你自己建一个FIND_IN_SET函数。。。自己建,那我还是选择把FIND_IN_SET改成IN吧,不支持这个,IN总支持吧。所以改成 IN 就是我的解决方案。
关于MySQL的符号 `
MySQL中,一般如果字段是关键字,会推荐加上符号 ` ,还有使用表名也有的时候可能会有这个符号,像这样:
select `name`,`password`
from `sys_user`
然后要注意,达梦不管是新版本还是旧版本,都没有这个符号,不支持这么写,所以改的时候看到这个符号记得去掉。
关于达梦的管理工具
改sql一般我会把sql复制到达梦的管理工具里,执行一下,看看能不能执行成功。然后那些sql语句,指定表名时,需要在前面加上模式名,如下:
select *
from DEMO.SYS_USER
不想加模式名的话,解决方法看这里:达梦数据库,写 SQL 如何才能不带上模式名?
第二个需要注意的就是,用管理工具,执行增删改语句时,执行完了需要提交事务才会生效。不然你会像我一样,搞半天都想不明白为什么语句都执行成功了,却没有生效。
还有就是浏览表数据的时候,你改了表数据,也需要先保存才算是更改成功了。
最后
(这是一篇去年写的文章了,当时一直忘了没发出来。)
更多的语法和函数用法,请参考:达梦技术文档-SQL开发指南
如果还是有疑问的话,可以去问答区找找:达梦技术社区-问答区