最近项目在做一个需求:要求书写mpper.xml里面的sql语句同时兼容Oracle数据库跟mysql数据库的支持
ORACLE和MYSQL作为两款使用最广泛的关系型数据库软件,在各项功能上以及编程语法上还是存在很大的差异的,因此要实现将系统从ORACLE迁移至MYSQL数据库上,数据的迁移仅仅是一方面,最大的挑战在于代码层面的改动,整个迁移的大致工作如下:
先给大家说一下思路:我们就是通过一套sql来支持对两个数据库语法的支持,采用动态切换连接数据源来判断连接的是oracle数据库还是mysq数据库。
配置文件及xml的部分事例:
1、ORACLE与MYSQL功能上的区别:
(1)字段类型的对比:
序号 | ORACLE | MYSQL |
---|---|---|
1 | VARCHAR2 | VARCHAR |
2 | DATE | DATETIME |
3 | TIMESTAMP | DATETIME |
4 | NUMBER | DECIMAL |
5 | INTEGER | DECIMAL(22,0) |
6 | CLOB | TEXT |
7 | BLOB | LONGBLOB |
(2)常用功能语法上的对比:
序号 | 对比项 | ORACLE | MYSQL |
---|---|---|---|
1 | 空字符的判断 | NAME IS NULL | NAME=’ ’ |
2 | from | select 1 from dual; from后边接表名 | select 1; from不是必须的,且无需接表名 |
3 | like的用法 | NAME like ‘a%’ 大小写敏感,只查询以小写字母a开头的字符串 | NAME like ‘a%’ 大小写不敏感,查询以小写字母a或大写字母A开头的字符串 |
4 | 日期格式化 | 获取系统日期:SYSDATE() 格式化日期:TO_CHAR、TO_DATE函数 | 获取系统日期:now() 格式化日期:str_to_date、date_format函数 |
主键 | 一般通过Oracle序列生成: SEQ_TEST.NEXTVAL SEQ_TEST.CURRVAL | Auto_increment属性实现自增获得唯一值 | |
5 | 分页(常用写法) | SELECT T2.* FROM (SELECT T1.*, ROWNUM RN FROM (SELECT * FROM TEST ORDER BY SID DESC) T1 WHERE ROWNUM < 5) T2 WHERE RN >= 1 | SELECT * FROM TEST_TEST1 ORDER BY SID DESC LIMIT 0,4 |
6 | 字符串链接 | SELECT sid || username|| PASSWORD FROM TEST SELECT concat(concat(sid , username), password) FROM TEST | SELECT CONCAT(SID , USERNAME, PASSWORD) FROM TEST |
7 | 分组函数 | SELECT EMPNO,JOB, COUNT(1), SUM(SAL),SUM(COMM) FROM EMP; 以上SQL不能正常执行,select后面的列必须是分组的列或者是用了聚合函数的列 | SELECT EMPNO,JOB, COUNT(1), SUM(SAL),SUM(COMM) FROM EMP; 随便分组都可以 |
8 | 分析函数 | row_number() over (partition by xx order by xx) rank() over (partition by xx order by xx) dense_rank() over (partition by xx order by xx) count(1) over() | Mysql不支持 |
9 | 并行 | 支持SQL级别并发处理 | 不支持 |
(3)数据库对象类型对比:
序号 | 数据库对象类型 | ORACLE | MYSQL | 迁移方案 |
---|---|---|---|---|
1 | PROCEDURE | (1)建存储过程用create procedure XXX 或 create or replace procedure XXX 两种语法 (2)存储过程参数不能指定精度或长度,如P_NAME VARCHAR2 (3)参数后必须要有IS或AS (4)存储过程没有参数时()必须省略 (5)变量定义在is和begin之间 | (1) 建存储过程只能用create procedure XXX一种语法 (2) 存储过程参数必须指定精度或长度,如P_NAME VARCHAR(100) (3) 参数后不能有IS或AS (4) 存储过程没有参数时必须保留() (5) 变量定义在begin和end之间 | 由于ORACLE跟MYSQL编写存储过程的语法差异,需重新修改存储过程代码 |
2 | TRIGGER | (1)包含DML触发器、替代触发器(视图)、系统触发器(DDL语句/系统事件) (2)支持语句级触发器和行级触发器 (3)一个触发器允许定义多个事件 | (1)只支持DML触发器 (2)只支持行级触发器 (3)一个触发器只允许定义一个事件 | 由于语法差异以及触发器功能实现的差异,需修改触发器代码 |
3 | FUNCTION | 支持 | 支持 | 设计到语法差距以及内置函数的差别需重新改写代码 |
4 | PACKAGE | 支持 | 不支持 | 需将PACKAGE使用存储过程替换 |
5 | VIEW | 支持 | 支持 | 如涉及到使用MYSQL不支持的内置函数需转换 |
6 | Materialized view | 支持 | 不支持 | 采用视图替换或者修改代码直接方案基表方式 |
7 | JOB | 支持比较完善 | 支持比较简单 | 按需修改 |
8 | DBLINK | 支持 | 无 | 可使用FEDERATED引擎实现dblink访问功能,需修改程序代码 |
9 | SEQUENCE | 支持 | 无 | 可使用MYSQL自增列实现序列功能,需修改表结构添加自增列 |
10 | SYNONYMS | 支持 | 无 | 需修改程序代码直接访问物理表 |
11 | CURSOR | (1)静态游标(隐式游标,显示游标)、ref游标 (2)支持loop循环、while循环、for循环 (3)支持记录变量 (4)支持bulk collection语法批量操作 | (1)只支持静态游标 (2)支持loop循环、repeat循环、while循环 (3)不支持记录变量 (4)只能单条操作 | 由于功能上的差距,需修改CURSOR代码 |
2、ORACLE数据迁移至MYSQL
(1)字段类型的调整:
数据迁移主要需要注意的地方在于字段类型支持的差异上,比如ORACLE常用的VARCHAR2类型以及CLOB类型在MYSQL中都不存在,因此我们需要稍微调整下字段类型,针对我们数据库目前的现状,有如下字段类型需要修改:
DATA_TYPE | COUNT(*) | 整改措施 |
---|---|---|
VARCHAR2 | 3194 | 使用MYSQL VARCHAR类型替代 |
NUMBER | 1522 | 使用MYSQL DECIMAL类型替代 |
DATE | 776 | 使用MYSQL DATETIME类型替代 |
CHAR | 544 | 无需整改 |
CLOB | 89 | 使用MYSQL TEXT类型替代 |
NVARCHAR2 | 34 | 使用MYSQL VARCHAR类型替代 |
TIMESTAMP(6) | 16 | 使用MYSQL DATETIME类型替代 |
BLOB | 12 | 使用MYSQL LONGBLOB类型替代 |
FLOAT | 6 | 无需整改 |
LONG RAW | 4 | 使用MYSQL LONGTEXT类型替代 |
LONG | 2 | 使用MYSQL LONGTEXT类型替代 |
(2)数据迁移:
目前MYSQL官方发布了一个将数据由SQL Server或Oracle中移植到MySQL中的工具包MySQLMigration Toolkit,该工具支持LOB字段数据的迁移,由于个创数据库目前的数据量相对较少,数据量目前统计将近17G左右,可以在停机的状态下使用该工具进行数据迁移,经过统计,目前个创数据库的表清单数量以及数据量分布如下:
OWNER | 数量 | 数据量 |
---|---|---|
QHIEX_PROD | 357 | 6.7G |
QHIEX | 162 | 10G |
(3)函数上的差异及一些常见的区别
编号 | 类别 | ORACLE | MYSQL | 注释 |
---|---|---|---|---|
1 | 数字函数 | round(1.23456,4) | round(1.23456,4) | 一样: ORACLE:select round(1.23456,4) value from dual MYSQL:select round(1.23456,4) value |
2 | abs(-1) | abs(-1) | 功能: 将当前数据取绝对值 用法: oracle和mysql用法一样 mysql: select abs(-1) value oracle: select abs(-1) value from dual | |
3 | ceil(-1.001)) | ceiling(-1.001) | 功能: 返回不小于 X 的最小整数 用法: mysqls: select ceiling(-1.001) value oracle: select ceil(-1.001) value from dual | |
4 | floor(-1.001) | floor(-1.001) | 功能: 返回不大于 X 的最大整数值 用法: mysql: select floor(-1.001) value oracle: select floor(-1.001) value from dual | |
5 | Max(expr)/Min(expr) | Max(expr)/Min(expr) | 功能:返回 expr 的最小或最大值。MIN() 和 MAX() 可以接受一个字符串参数;在这 种情况下,它们将返回最小或最大的字符串传下。 用法: ROACLE: select max(user_int_key) from sd_usr; MYSQL: select max(user_int_key) from sd_usr; | |
6 | 字符串函数 | ascii(str) | ascii(str) | 功能:返回字符串 str 最左边的那个字符的 ASCII 码值。如果 str 是一个空字符串, 那么返回值为 0。如果 str 是一个 NULL,返回值也是 NULL. 用法: mysql:select ascii(‘a’) value oracle:select ascii(‘a’) value from dual |
7 | CHAR(N,…) | CHAR(N,…) | 功能:CHAR() 以整数类型解释参数,返回这个整数所代表的 ASCII 码值给出的字符 组成的字符串。NULL 值将被忽略. 用法: mysql:select char(97) value oracle:select chr(97) value from dual | |
8 | REPLACE(str,from_str,to_str) | REPLACE(str,from_str,to_str) | 功能: 在字符串 str 中所有出现的字符串 from_str 均被 to_str 替换,然后返回这个字符串. 用法: mysql: SELECT REPLACE(‘abcdef’, ‘bcd’, ‘ijklmn’) value oracle: SELECT Replace(‘abcdef’, ‘bcd’, ‘ijklmn’) value from dual | |
9 | INSTR(‘sdsq’,‘s’,2) | INSTR(‘sdsq’,‘s’) | 参数个数不同 ORACLE: select INSTR(‘sdsq’,‘s’,2) value from dual(要求从位置2开始) MYSQL: select INSTR(‘sdsq’,‘s’) value(从默认的位置1开始) | |
10 | SUBSTR(‘abcd’,2,2) | substring(‘abcd’,2,2) | 函数名称不同: ORACLE: select substr(‘abcd’,2,2) value from dual MYSQL: select substring(‘abcd’,2,2) value | |
11 | instr(‘abcdefg’,’ab’) | locate(‘ab’,’abcdefg’) | 函数名称不同: instr -> locate(注意:locate的子串和总串的位置要互换) ORACLE: SELECT instr(‘abcdefg’, ‘ab’) VALUE FROM DUAL MYSQL: SELECT locate(‘ab’, ‘abcdefg’) VALUE | |
12 | length(str) | char_length() | 函数名称不同: ORACEL: SELECT length(‘AAAASDF’) VALUE FROM DUAL MYSQL: SELECT char_length(‘AAAASDF’) VALUE | |
13 | REPLACE(‘abcdef’, ‘bcd’, ‘ijklmn’) | REPLACE(‘abcdef’, ‘bcd’, ‘ijklmn’) | 一样: ORACLE: SELECT REPLACE(‘abcdef’, ‘bcd’, ‘ijklmn’) value from dual MYSQL: SELECT REPLACE(‘abcdef’, ‘bcd’, ‘ijklmn’) value | |
14 | LPAD(‘abcd’,14, ‘0’) | LPAD(‘abcd’,14, ‘0’) | 一样: ORACLE: select LPAD(‘abcd’,14, ‘0’) value from dual MYSQL: select LPAD(‘abcd’,14, ‘0’) value from dual | |
15 | UPPER(iv_user_id) | UPPER(iv_user_id) | 一样: ORACLE: select UPPER(user_id) from sd_usr; MYSQL: select UPPER(user_id) from sd_usr; | |
16 | LOWER(iv_user_id) | LOWER(iv_user_id) | 一样: ORACLE: select LOWER(user_id) from sd_usr; MYSQL: select LOWER(user_id) from sd_usr; | |
17 | 控制流函数 | nvl(u.email_address, 10) | IFNULL(u.email_address, 10) 或 ISNULL(u.email_address) | 函数名称不同(根据不同的作用进行选择): ORACLE: select u.email_address, nvl(u.email_address, 10) value from sd_usr u (如果u.email_address=NULl,就在DB中用10替换其值) MYSQL: select u.email_address, IFNULL(u.email_address, 10) value from sd_usr u(如果u.email_address=NULl,显示结果中是10,而不是在DB中用10替换其值) select u.email_address, ISNULL(u.email_address) value from sd_usr u(如果u.email_address是NULL, 就显示1,否则就显示0) |
18 | DECODE(iv_sr_status,g_sr_status_com, ld_sys_date, NULL) | 无,请用IF或CASE语句代替. IF语句格式:(expr1,expr2,expr3) | 说明: 1. decode(条件,值1,翻译值1,值2,翻译值2,…值n,翻译值n,缺省值) 该函数的含义如下: IF 条件=值1 THEN RETURN(翻译值1) ELSIF 条件=值2 THEN RETURN(翻译值2) … ELSIF 条件=值n THEN RETURN(翻译值n) ELSE RETURN(缺省值) END IF 2. mysql If语法说明 功能: 如果 expr1 是TRUE (expr1 <> 0 and expr1 <> NULL),则 IF()的返回值为expr2; 否则返回值则为 expr3。IF() 的返回值为数字值或字符串值,具体情况视其所在 语境而定。 用法: mysql: SELECT IF(1>2,2,3); | |
19 | 类型转换函数 | TO_CHAR(SQLCODE) | date_format/ time_format | 函数名称不同 SQL> select to_char(sysdate,‘yyyy-mm-dd’) from dual; SQL> select to_char(sysdate,‘hh24-mi-ss’) from dual; mysql> select date_format(now(),’%Y-%m-%d’); mysql> select time_format(now(),’%H-%i-%S’); |
20 | to_date(str,format) | STR_TO_DATE(str,format) | 函数名称不同: ORACLE:SELECT to_date(‘2009-3-6’,‘yyyy-mm-dd’) VAULE FROM DUAL MYSQL: SELECT STR_TO_DATE(‘2004-03-01’, ‘%Y-%m-%d’) VAULE | |
21 | trunc(-1.002) | cast(-1.002 as SIGNED) | 函数名称不同: TRUNC函数为指定元素而截去的日期值。 ORACLE: select trunc(-1.002) value from dual MYSQL:select cast(-1.002 as SIGNED) value MYSQL: 字符集转换 : CONVERT(xxx USING gb2312) 类型转换和SQL Server一样,就是类型参数有点点不同 : CAST(xxx AS 类型) , CONVERT(xxx,类型),类型必须用下列的类型: 可用的类型 二进制,同带binary前缀的效果 : BINARY 字符型,可带参数 : CHAR() 日期 : DATE 时间: TIME 日期时间型 : DATETIME 浮点数 : DECIMAL 整数 : SIGNED 无符号整数 : UNSIGNED | |
22 | TO_NUMBER(str) | CAST(“123” AS SIGNED INTEGER) | 函数名称不同 ORACLE:SELECT TO_NUMBER(‘123’) AS VALUE FROM DUAL; MYSQL: SELECT CAST(“123” AS SIGNED INTEGER) as value; SIGNED INTEGER:带符号的整形 | |
23 | 日期函数 | SYSDATE | now() / SYSDATE() | 写法不同: ORACLE:select SYSDATE value from dual MYSQL:select now() value select sysdate() value |
24 | Next_day(sysdate,7) | 自定义一个函数:F_COMMON_NEXT_DAY(date,int) | 函数名称不同: ORACLE: SELECT Next_day(sysdate,7) value FROM DUAL MYSQL: SELECT F_COMMON_NEXT_DAY(SYSDATE(), 3) value from DUAL; (3:指星期的索引值)返回的指定的紧接着下一个星期的日期 | |
25 | ADD_MONTHS(sysdate, 2) | DATE_ADD(sysdate(), interval 2 month) | 函数名称不同: ORACLE: SELECT ADD_MONTHS(sysdate, 2) as value from DUAL; MYSQL: SELECT DATE_ADD(sysdate(), interval 2 month) as value from DUAL; | |
26 | 2个日期相减(D1-D2) | DATEDIFF(date1,date2) | 功能: 返回两个日期之间的天数。 用法: mysql: SELECT DATEDIFF(‘2008-12-30’,‘2008-12-29’) AS DiffDate oracle: 直接用两个日期相减(比如d1-d2=12.3) | |
27 | SQL函数 | SQLCODE | MYSQL中没有对应的函数,但JAVA中SQLException。getErrorCode()函数可以获取错误号 | Oracle内置函数SQLCODE和SQLERRM是特别用在OTHERS处理器中,分别用来返回Oracle的错误代码和错误消息。 MYSQL: 可以从JAVA中得到错误代码,错误状态和错误消息 |
28 | SQLERRM | MYSQL中没有对应的函数,但JAVA中SQLException。getMessage()函数可以获取错误消息 | Oracle内置函数SQLCODE和SQLERRM是特别用在OTHERS处理器中,分别用来返回Oracle的错误代码和错误消息。 MYSQL: 可以从JAVA中得到错误代码,错误状态和错误消息 | |
29 | SEQ_BK_DTL_OPT_INT_KEY.NEXTVAL | 自动增长列 | 在MYSQL中是自动增长列. 如下方法获取最新ID: START TRANSACTION; INSERT INTO user(username,password) VALUES (username,MD5(password)); SELECT LAST_INSERT_ID() INTO id; COMMIT; | |
30 | SUM(enable_flag) | SUM(enable_flag) | 一样: ORCALE: SELECT SUM(enable_flag) FROM SD_USR; MYSQL: SELECT SUM(enable_flag) FROM SD_USR; | |
31 | DBMS_OUTPUT.PUT_LINE(SQLCODE) | 在MYSQL中无相应的方法,其作用是在控制台中打印,用于测试,对迁移无影响。 | dbms_output.put_line每行只能显示255个字符,超过了就会报错 | |
1、连接字符串在Oracle中用 | ,SqlServer中用+,MySQL中用concat(‘a’,‘b’,‘c’) |
2、orcale 生成唯一序列是 select sys.guid() from dual ,mysql是 select uuid() from dual
3、mysql可以实现自增长主键(通过字段的auto_increment属性);Oracle则需要通过序列(Sequence)来实现。
4、mysql可以用双引号来引用字符串(当然单引号也行);Oracle只能用单引号。
5、mysql在查询语句中可以通过limit [offset,] 来直接分页;而Oracle需要使用rownum伪列。
6、mysql对于真假的判断,0为假1为真;Oracle则是用true/false。
7、mysql的查询可以 select sysdate(); ;而Oracle需要引用虚表(select sysdate from dual;)。
8、mysql对于like的查询,CONCAT(’%’, #{name,jdbcType=VARCHAR},’%’) ;Oracle则是用LIKE ‘%’||#{name,jdbcType=VARCHAR}||’%’
9、mysql的查询可以 select sysdate(); ;而Oracle需要引用虚表(select sysdate from dual;)。
10、mysql中备份命令:mysqldump,执行结果是一个sql文件;oracle备份命令:dpdump,执行结果是一个dmp文件。前件是文本sql命令,
可以直接导入到其它mysql数据库,甚至可以稍作修改导入到其它类型的数据库;后者导出文件是二进制的,只能Oracle自己用(甚至还有版本限制)。
11、mysql中的命令默认是直接commit的;Oracle默认不是
12、mysql中日期的转换用dateformat()函数;Oracle用to_date()与to_char()两个函数。
13、mysql在Windows环境下大小写是不敏感的;unix/linux环境下,对数据库名、表名大小写敏感,列名大小写不敏感。Oracle则不论环境大小写都不敏感。
14、mysql支持枚举类型(enum)、集合类型(set);Oracle不直接支持,需要使用外键等其它手段实现。
15.Oracle中的decode在mysql中的等价实现
mysql支持if
格式:
IF(expr1,expr2,expr3)
如果expr1是TRUE(expr1<>;0且expr1<>;NULL),那么IF()返回expr2,否则它返回expr3。IF()返回一个数字或字符串值
oracle的写法
SELECT decode(ttype,1,’a',2,’b',3,’c',’d') FROM taba
可以在mysql里写成
SELECT if(ttype=1, 'a',if(ttype=2,'b', if(ttype =3, 'c', 'd'))) FROM taba
3、系统测试(包括数据迁移的测试)
程序经过大量的修改后,测试以及BUG的修复往往是耗时最长也是最为重要的一环,各个功能都需要经过全面的测试以及验证工作,从而避免生出上线后出现的各种问题