因为TDSQL兼容MySQL的语法,所以下文中用MySQL的语法替代TDSQL,本文重点关注语法转换。

一、 基础语法差异与迁移

  1. 字符串连接:

    • Oracle: || (例如 SELECT 'Hello' || ' World' FROM dual;)
    • MySQL: CONCAT() 函数 (例如 SELECT CONCAT('Hello', ' World');)。|| 在MySQL中默认是逻辑OR运算符(除非设置SQL_MODE=PIPES_AS_CONCAT,但不推荐依赖此设置)。
    • 迁移: 将所有 || 替换为 CONCAT()
  2. 空字符串与NULL:

    • Oracle: 严格区分空字符串('')和NULL
    • MySQL: 不严格区分。在大多数上下文中(特别是使用=<>比较时),空字符串('')被视为等同于NULL。但使用IS NULLIS NOT NULL时能区分。
    • 迁移: 这是最容易出错的地方!
      • 仔细检查所有使用 column = ''column <> '' 的条件。通常需要改为 column IS NULL(column IS NULL OR column = '')(column IS NOT NULL AND column <> ''),具体逻辑取决于Oracle中的原始意图。
      • 检查NVL/NVL2等处理空值的函数逻辑,确保在MySQL中达到相同效果(MySQL的IFNULL/COALESCE处理NULL,不处理空串)。
  3. 伪表 DUAL:

    • Oracle: SELECT SYSDATE FROM dual; SELECT 1+1 FROM dual; (DUAL是单行单列虚拟表)
    • MySQL: SELECT NOW(); SELECT 1+1; (可以直接执行,不需要FROM子句)
    • 迁移: 删除不必要的 FROM dual
  4. 分页查询:

    • Oracle 12c 之前: 使用 ROWNUM 和子查询实现复杂分页。
    • Oracle 12c+: 使用 OFFSET ... FETCH ...
    • MySQL: 使用 LIMIT [offset,] row_countLIMIT row_count OFFSET offset
    • 迁移:
      • 将Oracle 12c+的 OFFSET ... FETCH ... 直接替换为MySQL的 LIMIT ... OFFSET ... 语法。
      • 将Oracle 12c之前基于ROWNUM的复杂分页逻辑重写为使用LIMIT ... OFFSET ...。通常需要去掉外层包装的子查询。
  5. 日期和时间:

    • 获取当前时间:
      • Oracle: SYSDATE (精确到秒), SYSTIMESTAMP (精确到小数秒)
      • MySQL: NOW() (返回DATETIME,精确到秒), CURRENT_TIMESTAMP() (同NOW()), SYSDATE() (每次调用实时获取,可能影响基于语句的复制和某些优化), CURDATE(), CURTIME()。推荐使用 NOW()CURRENT_TIMESTAMP()
    • 日期加减:
      • Oracle: date_column + INTERVAL '1' DAY, date_column - 7
      • MySQL: DATE_ADD(date_column, INTERVAL 1 DAY), DATE_SUB(date_column, INTERVAL 7 DAY)date_column + INTERVAL 1 DAY, date_column - INTERVAL 7 DAY (MySQL也支持 +/- INTERVAL 语法,推荐)。
    • 日期截断:
      • Oracle: TRUNC(date_column) (到天), TRUNC(date_column, 'MM') (到月)
      • MySQL: DATE(date_column) (提取日期部分), LAST_DAY(date_column) (月末), 对于其他截断通常使用 DATE_FORMAT(date_column, '%Y-%m-01') (月初) 或组合函数。
    • 日期格式化:
      • Oracle: TO_CHAR(date_column, 'YYYY-MM-DD HH24:MI:SS')
      • MySQL: DATE_FORMAT(date_column, '%Y-%m-%d %H:%i:%s') (注意:%H是24小时制, %i是分钟)
    • 字符串转日期:
      • Oracle: TO_DATE('2023-10-27', 'YYYY-MM-DD')
      • MySQL: STR_TO_DATE('2023-10-27', '%Y-%m-%d')
    • 迁移: 系统性地替换日期相关函数和运算符。特别注意格式模型的不同。
  6. 序列 (Sequence):

    • Oracle: 使用显式的SEQUENCE对象 (CREATE SEQUENCE ...), 通过 sequence_name.NEXTVALsequence_name.CURRVAL 访问。
    • MySQL: 使用 AUTO_INCREMENT 属性为主键列自动生成唯一ID (主要用于单表)。对于需要全局序列或多表共享序列,有以下方案:
      • 方案A (推荐): 创建一个专门的表来模拟序列 (包含 id AUTO_INCREMENT 字段)。通过 INSERT ... ; SELECT LAST_INSERT_ID(); 获取新值。需要小心并发和事务管理。
      • 方案B: 使用MySQL 8.0+的 AUTO_INCREMENT 的持久化特性(innodb_autoinc_lock_mode=2 - interleaved lock mode)结合 LAST_INSERT_ID(),但这主要解决批量插入的间隙问题,不完全等同于独立序列。
      • 方案C (谨慎): 使用第三方工具或自定义函数。
    • 迁移: 对于主键,优先使用AUTO_INCREMENT。对于非主键序列需求,采用方案A(模拟序列表)并重写所有 NEXTVAL/CURRVAL 的访问逻辑。
  7. 数据类型映射:

    • 常用映射:
      • NUMBER(p, s) -> DECIMAL(p, s) (精确数值)
      • VARCHAR2(n [CHAR|BYTE]) -> VARCHAR(n) (MySQL的VARCHAR长度指字符数,需确认n是否足够,注意UTF8MB4字符可能占4字节)
      • NVARCHAR2(n) -> NVARCHAR(n)VARCHAR(n) CHARACTER SET utf8mb4 (存储Unicode)
      • DATE -> DATETIMEDATE (Oracle DATE包含时间部分,MySQL DATE只有日期。如果需要时间,用DATETIMETIMESTAMP)
      • TIMESTAMP -> TIMESTAMP (注意MySQL TIMESTAMP范围是’1970-01-01 00:00:01’ UTC 到 ‘2038-01-19 03:14:07’ UTC, 且有时区转换行为) 或 DATETIME (范围’1000-01-01 00:00:00’到’9999-12-31 23:59:59’, 无时区转换)
      • CLOB -> LONGTEXT
      • BLOB -> LONGBLOB
      • RAW(n) -> VARBINARY(n)
      • LONG -> LONGTEXTLONGBLOB (已废弃类型)
    • 迁移: 根据实际存储内容和需求仔细选择最合适的MySQL类型。特别注意日期/时间类型的选择和TIMESTAMP的范围限制。

二、 函数映射与替换

  1. 空值处理:

    • NVL(expr1, expr2) -> IFNULL(expr1, expr2)COALESCE(expr1, expr2)
    • NVL2(expr1, expr2, expr3) -> IF(expr1 IS NOT NULL, expr2, expr3)CASE WHEN expr1 IS NOT NULL THEN expr2 ELSE expr3 END
    • COALESCE(expr1, expr2, ..., exprn) (两者都有,语法相同)
  2. 条件逻辑:

    • DECODE(value, search1, result1, search2, result2, ..., default) -> CASE value WHEN search1 THEN result1 WHEN search2 THEN result2 ... ELSE default END
    • CASE ... END (两者语法基本相同,是首选)
  3. 字符串函数:

    • SUBSTR(string, start [, length]) -> SUBSTRING(string, start [, length]) (注意:Oracle的start可为负表示从末尾数,MySQL也支持负数start)
    • INSTR(string, substring [, start [, occurrence]]) -> LOCATE(substring, string [, start]) (MySQL的LOCATE只返回第一次出现的位置,要模拟occurrence需要更复杂逻辑或自定义函数)
    • LENGTH(string) -> CHAR_LENGTH(string)LENGTH(string) (Oracle LENGTH按字符数,MySQL LENGTH()按字节数, CHAR_LENGTH()按字符数。强烈推荐在涉及多字节字符集时使用CHAR_LENGTH())
    • LPAD(string, length [, pad_string]) / RPAD(...) (两者都有,语法相同)
    • UPPER(string) / LOWER(string) (两者都有,语法相同)
    • TRIM([[LEADING|TRAILING|BOTH] trim_character FROM] string) (两者语法基本相同)
    • REPLACE(string, search_string, replacement_string) (两者都有,语法相同)
  4. 聚合函数:

    • COUNT, SUM, AVG, MIN, MAX (两者语法基本相同)
    • LISTAGG(measure_expr [, delimiter]) WITHIN GROUP (ORDER BY sort_expr) -> GROUP_CONCAT([DISTINCT] expr [, separator] [ORDER BY ... [ASC|DESC]]) (注意排序和分隔符位置语法差异)
  5. 分析函数 (窗口函数):

    • Oracle有丰富的分析函数 (ROW_NUMBER(), RANK(), DENSE_RANK(), LEAD(), LAG(), SUM() OVER(), ...)
    • MySQL 8.0+ 已支持标准SQL窗口函数,语法与Oracle高度相似。 这是迁移到MySQL 8.0+的巨大优势!
    • 迁移: 如果使用MySQL 8.0+,大部分分析函数可以直接迁移或做极小语法调整(如别名引用)。如果使用MySQL 5.7或更低版本,需要彻底重写为使用变量(@var)模拟或复杂的自连接/子查询,工作量巨大且性能可能不佳。强烈建议升级到MySQL 8.0+以支持窗口函数。
  6. 其他常用函数:

    • TO_CHAR(number/date, format) -> 数字: FORMAT(number, decimals) (注意返回字符串带千分位) 或 CAST(number AS CHAR);日期: DATE_FORMAT(date, format)
    • TO_NUMBER(string) -> CAST(string AS DECIMAL)CONVERT(string, DECIMAL)
    • SYSDATE -> NOW()CURRENT_TIMESTAMP()
    • ADD_MONTHS(date, n) -> DATE_ADD(date, INTERVAL n MONTH)
    • MONTHS_BETWEEN(date1, date2) -> 需计算:TIMESTAMPDIFF(MONTH, date2, date1) + 调整 (注意Oracle结果含小数部分表示不足月的天数差异,精确模拟较复杂)
    • LAST_DAY(date) (两者都有)
    • NEXT_DAY(date, 'DAYOFWEEK') -> 需要计算:DATE_ADD(date, INTERVAL (7 - WEEKDAY(date) + CASE WHEN 'target_dow_index' > WEEKDAY(date) THEN 'target_dow_index' - WEEKDAY(date) ELSE 7 - WEEKDAY(date) + 'target_dow_index' END) DAY) (很复杂,通常建议应用层处理或自定义函数)
    • DBMS_RANDOM.VALUE -> RAND() (生成0-1随机浮点数)

三、 高级特性与对象迁移

  1. 存储过程、函数、包:

    • 语法差异巨大: PL/SQL (Oracle) vs MySQL的存储过程语言(基于SQL/PSM)。包(Package)在MySQL中没有直接对应概念。
    • 迁移策略:
      • 重构: 这是最彻底但也最耗时的方式。将Oracle的PL/SQL代码(尤其是使用了大量Oracle特有函数、特性、游标、异常处理的代码)用MySQL的存储过程/函数语法重写。
      • 分解包: 将Oracle包中的存储过程、函数、变量、游标定义拆分到独立的MySQL存储过程、函数、临时表/会话变量中。
      • 工具转换+人工重写: 使用迁移工具进行初步转换,然后投入大量精力进行人工审查、测试和重写。
      • 业务逻辑上移: 考虑是否可以将部分逻辑移到应用层代码中实现,减少对数据库存储过程的依赖。
  2. 触发器:

    • 语法 (CREATE TRIGGER ... BEFORE/AFTER ... ON ... FOR EACH ROW ...) 基本相似。
    • 迁移注意点:
      • 替换触发器内部使用的Oracle特有函数和语法(如 :NEW, :OLD 引用新/旧行在MySQL中写法相同)。
      • 注意MySQL的触发器不允许在触发器中调用存储过程(MySQL 5.7及之前)或对本表进行修改(可能导致递归循环)。MySQL 8.0允许在触发器中调用存储过程。
      • 仔细测试触发器逻辑,确保在MySQL中行为一致。
  3. 视图:

    • 语法 (CREATE VIEW ... AS SELECT ...) 基本相同。
    • 迁移: 主要工作是确保视图定义中的SQL语句本身(涉及到的表、列、函数、条件)在MySQL中能正确执行并返回预期结果。替换其中的Oracle特有语法和函数。
  4. 索引与约束:

    • 主键、外键、唯一约束、非空约束: 语法 (PRIMARY KEY, FOREIGN KEY ... REFERENCES ..., UNIQUE, NOT NULL) 基本相同,迁移时保留即可。
    • 检查约束: Oracle支持,MySQL直到8.0.16才真正支持标准的CHECK约束(之前版本会解析但忽略)。迁移到MySQL 8.0.16+: 可以迁移CHECK约束。迁移到更低版本: 约束逻辑需要通过触发器或在应用层实现。
    • 函数索引: Oracle支持。MySQL 8.0.13+支持在InnoDB上创建函数索引(称为Generated Columns Index)。迁移策略: 如果使用MySQL 8.0.13+,可以考虑使用Generated Column+索引模拟。否则,需要重写查询避免在WHERE条件中对列使用函数。
  5. 事务与锁:

    • 基本语法 (START TRANSACTION, COMMIT, ROLLBACK, SAVEPOINT) 相同。
    • 隔离级别:
      • Oracle默认通常是READ COMMITTED
      • MySQL InnoDB默认是REPEATABLE READ行为有显著差异! (如MySQL RR通过快照避免不可重复读和幻读,Oracle RC可能遇到)。迁移: 评估应用对隔离级别的依赖。可以在MySQL连接会话或全局设置隔离级别 (SET TRANSACTION ISOLATION LEVEL READ COMMITTED;)。务必进行并发测试。
    • 锁机制: 两者都是基于锁+MVCC。理解差异对于高性能应用很重要,但SQL语法层面通常不需要修改。
  6. 层次查询 (CONNECT BY):

    • Oracle: 使用 START WITH ... CONNECT BY [PRIOR] ... 进行递归查询。
    • MySQL 8.0+: 使用标准SQL的递归公用表表达式 WITH RECURSIVE cte_name AS (...) SELECT ... FROM cte_name
    • 迁移: 如果使用MySQL 8.0+,将CONNECT BY查询重写为WITH RECURSIVE语法。这是结构性的重写,需要理解递归CTE原理。低版本MySQL无内置支持,需要应用层递归或使用存储过程模拟,非常复杂。

四、 字符集与排序规则

  • Oracle: 常用AL32UTF8 (UTF-8)。
  • MySQL: 强烈推荐使用 utf8mb4 字符集和 utf8mb4_0900_ai_ci (或合适的) 排序规则。
    • utf8mb4 是真正的UTF-8,支持4字节字符(如emoji表情)。
    • MySQL旧的utf8只支持3字节字符(已废弃)。
  • 迁移: 确保在MySQL服务器、数据库、表和连接字符串中都明确指定使用 utf8mb4 和合适的排序规则 (collation)。排序规则影响字符串比较和排序规则,需根据业务需求选择(如是否区分大小写 _ci/_cs,是否区分重音 _ai/_as)。测试字符数据的正确存储、检索和比较。

关键挑战与注意事项

  1. 存储过程/包/高级函数: 最大的技术难点,需要深入理解两边语法和特性,可能涉及大量重写。
  2. 隐式行为差异: 空字符串=NULL、日期处理、隐式类型转换、默认隔离级别等,容易在测试中遗漏,导致生产环境问题。
  3. 性能调优: MySQL的优化器、锁机制、执行计划可能与Oracle不同。迁移后必须进行性能测试和优化(索引、查询重写、参数调整)。
  4. 事务与并发控制: 理解REPEATABLE READREAD COMMITTED的差异对应用并发逻辑的影响。
  5. 字符集与编码: 确保utf8mb4正确配置,避免乱码问题。
  6. 测试覆盖度: 测试不充分是迁移失败的主要原因。需要全面的测试计划和数据。
  7. 人员技能: 团队需要同时熟悉Oracle和MySQL(特别是MySQL 8.0的新特性)。

总结: Oracle迁移到MySQL在SQL层面是一个系统性的工程,涉及大量语法、函数和特性的映射、重写与适配。成功的关键在于深入理解差异、严格的测试、对复杂代码(特别是存储过程)的重点投入以及选择MySQL 8.0+版本。务必做好详细规划、风险评估和充分的测试验证。祝您迁移顺利!