MySQL → Oracle 遷移涉及 SQL 方言差異、資料型別映射、PL/SQL 改寫、效能模型轉換、工具鏈選型等多維度挑戰。本文基於 Oracle SQL Developer Migration Workbench、第三方工具與手工改寫實踐,提供端到端遷移方法論。
| 工具 | 類型 | 支援版本 | 自動化程度 | 適用場景 | 授權成本 |
|---|---|---|---|---|---|
| Oracle SQL Developer Migration Workbench | 官方免費 GUI | MySQL 5.6/5.7/8.0 → Oracle 11g/12c/19c/21c | 中 (Schema 90%+,PL/SQL 需人工) | 中小型專案、預算有限、團隊有 Oracle 技能 | 免費 |
| Oracle Migration Workbench (舊版 OMWB) | 已整合入 SQL Developer | - | - | 歷史參考 | - |
| AWS Schema Conversion Tool (SCT) | 免費客戶端 | MySQL → Oracle/PostgreSQL/Aurora/Redshift | 高 (含應用程式 SQL 掃描轉換) | 雲遷移、大型專案、需應用層 SQL 改寫 | 免費 |
| Ispirer MnMTK / SQLWays | 商業化工具 | 全版本 | 極高 (含存儲過程、觸發器、應用代碼) | 核心業務、大量 PL/SQL、嚴格時限 | 高 (萬美金級) |
| 手工腳本 + pt-online-schema-change | 工程自研 | 全版本 | 低 (完全可控) | 極簡 Schema、特殊需求、零依賴 | 人力成本 |
| MySQL 型別 | Oracle 對應型別 | 注意事項 |
|---|---|---|
| TINYINT | NUMBER(3) | MySQL 無 Boolean,TINYINT(1) 常用作 Boolean |
| SMALLINT | NUMBER(5) | |
| MEDIUMINT | NUMBER(7) | |
| INT / INTEGER | NUMBER(10) | |
| BIGINT | NUMBER(19) | |
| DECIMAL(p,s) / NUMERIC(p,s) | NUMBER(p,s) | 精度完全對應 |
| FLOAT / DOUBLE | BINARY_FLOAT / BINARY_DOUBLE | Oracle NUMBER 為定點數,浮點運算用 BINARY_* |
| BIT(n) | RAW(n/8) 或 NUMBER(1) | BIT(1) → NUMBER(1) 最通用 |
| CHAR(n) | CHAR(n CHAR) | Oracle 預設 Byte 語義,必須指定 CHAR 語義 |
| VARCHAR(n) | VARCHAR2(n CHAR) | VARCHAR2 為標準,VARCHAR 保留關鍵字 |
| TINYTEXT (255) | VARCHAR2(255 CHAR) | |
| TEXT (64KB) | CLOB | 超過 4000 Byte 必須用 CLOB |
| MEDIUMTEXT (16MB) | CLOB | |
| LONGTEXT (4GB) | CLOB | |
| BLOB 系列 | BLOB | Oracle BLOB 統一 4GB 上限 |
| DATE | DATE | Oracle DATE 含時分秒,MySQL DATE 僅日期 |
| DATETIME / TIMESTAMP | TIMESTAMP(6) / TIMESTAMP(6) WITH LOCAL TIME ZONE | MySQL TIMESTAMP 自動時區轉換,Oracle 用 WITH LOCAL TIME ZONE |
| TIME | INTERVAL DAY TO SECOND | 或 NUMBER(秒數) |
| YEAR | NUMBER(4) | |
| ENUM | VARCHAR2 + CHECK CONSTRAINT | Oracle 無 ENUM,用 CHECK 约束模擬 |
| SET | VARCHAR2 (逗號分隔) 或 關聯表 | 建議正規化為關聯表 |
| JSON (5.7+) | JSON (12c+ / 19c+) | Oracle 12c 引入 JSON 型別,功能較 MySQL 弱 |
| GEOMETRY | SDO_GEOMETRY (Spatial) | 需 Spatial/Locator 選項 |
-- MySQL
CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));
-- Oracle 12c+ Identity Column (推薦)
CREATE TABLE t (
id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY,
name VARCHAR2(50 CHAR)
);
-- Oracle 11g Sequence + Trigger
CREATE SEQUENCE t_seq START WITH 1 INCREMENT BY 1 NOCACHE;
CREATE TABLE t (id NUMBER PRIMARY KEY, name VARCHAR2(50 CHAR));
CREATE OR REPLACE TRIGGER t_bir
BEFORE INSERT ON t FOR EACH ROW
WHEN (NEW.id IS NULL)
BEGIN
SELECT t_seq.NEXTVAL INTO :NEW.id FROM dual;
END;
/
-- MySQL
SELECT * FROM t ORDER BY id LIMIT 10 OFFSET 20;
-- Oracle 12c+ (標準 SQL)
SELECT * FROM t ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- Oracle 11g (子查詢 + ROWNUM)
SELECT * FROM (
SELECT a.*, ROWNUM rn FROM (
SELECT * FROM t ORDER BY id
) a WHERE ROWNUM <= 30
) WHERE rn > 20;
-- MySQL
INSERT INTO t (id, val) VALUES (1, 'a') ON DUPLICATE KEY UPDATE val = 'b';
-- Oracle MERGE
MERGE INTO t USING (SELECT 1 AS id, 'b' AS val FROM dual) src
ON (t.id = src.id)
WHEN MATCHED THEN UPDATE SET t.val = src.val
WHEN NOT MATCHED THEN INSERT (id, val) VALUES (src.id, src.val);
-- MySQL: IFNULL(a, b) / COALESCE(a, b, c)
-- Oracle: NVL(a, b) (僅兩參數) / COALESCE(a, b, c) (標準 SQL,推薦)
-- MySQL: DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s')
-- Oracle: TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS')
-- 注意: Oracle 格式模型區分大小寫,MI=分, MM=月
-- MySQL NOW() 返回當前會話時間 (受 time_zone 影響)
-- Oracle SYSDATE 返回資料庫伺服器時間 (不受會話影響)
-- Oracle SYSTIMESTAMP 含毫秒/微秒 + 時區
-- Oracle CURRENT_DATE 返回會話時區日期
-- MySQL
SELECT GROUP_CONCAT(name ORDER BY id SEPARATOR ',') FROM t GROUP BY type;
-- Oracle
SELECT LISTAGG(name, ',') WITHIN GROUP (ORDER BY id) FROM t GROUP BY type;
核心差異:
策略:邏輯複雜的存儲過程**不建議自動轉換**,需資深 PL/SQL 開發重寫,並建立單元測試驗證邏輯等價。
| 資料量 | 推薦方式 | 工具/方法 |
|---|---|---|
| < 100GB | 邏輯導出導入 | SQL Developer Data Pump / mysqldump → SQL*Loader / 外部表 |
| 100GB ~ 1TB | 並行邏輯 / 物理備份轉換 | mydumper + 並行 SQL*Loader / Oracle Data Pump (跨平台) |
| > 1TB | CDC 同步 + 歷史全量 | Debezium + Kafka → Oracle GoldenGate / 自研消費者 |
| 坑點 | 影響 | 規避措施 |
|---|---|---|
| CHAR/VARCHAR Byte vs Char 語義 | 中文存儲截斷/空間浪費 | 全庫統一 `CHAR` 語義:`VARCHAR2(n CHAR)`、`CHAR(n CHAR)` |
| DATE vs TIMESTAMP 時區 | 跨時區應用時間錯誤 | 核心時間欄位用 `TIMESTAMP WITH LOCAL TIME ZONE` |
| 空字串 '' vs NULL | MySQL '' ≠ NULL,Oracle '' = NULL | 遷移前統一語義:業務層決定是否允許空字串,Oracle 用 ' ' 或 NULL |
| 大小寫敏感度 | MySQL 預設不敏感 (utf8mb4_general_ci),Oracle 敏感 | Oracle 建立 `LINGUISTIC` 索引或應用層統一大小寫 |
| 隱含型別轉換 | 索引失效、效能差 | Oracle 嚴格型別檢查,應用層綁定變數必須匹配型別 |
| 鎖機制差異 | 死鎖模式不同 | Oracle 讀不阻寫、寫不阻讀 (MVCC),但 SELECT FOR UPDATE 行鎖語義不同 |
| 統計資訊收集 | 執行計劃劣化 | 遷移後必須 `DBMS_STATS.GATHER_SCHEMA_STATS`,建立收集排程 |
| 授權成本 | Enterprise Edition 選項 (Partitioning, Advanced Compression, RAC) 極貴 | 評估是否真正需要 EE,SE2 可能足夠 |
-- 表/欄位/索引/約束/視圖/同義詞 逐項對比
SELECT table_name, column_name, data_type, data_length, nullable, data_default
FROM user_tab_columns WHERE table_name IN (...)
MINUS
SELECT ... FROM mysql_schema_export;
-- 核心表: 行數、主鍵 Min/Max、Checksum (ORA_HASH/DBMS_CRYPTO)
SELECT COUNT(*), MIN(id), MAX(id), SUM(ORA_HASH(row_data)) FROM core_table;
-- 抽樣逐行對比 (關鍵業務表)
SELECT * FROM mysql_core_table
MINUS
SELECT * FROM oracle_core_table;
MySQL → Oracle 遷移不僅是技術遷移,更是**架構、成本、技能、風險**的綜合工程決策。若無強制合規/生態綁定需求,**MySQL 8.0 + InnoDB Cluster / MGR + 讀寫分離 + 分庫分表**往往能以 1/10 成本滿足 99% 業務場景。遷移前請三思:是否真正需要 Oracle?