首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫

SQL Server 到 MySQL 異構遷移實戰:ODBC 直導 vs PHP 腳本遷移深度對比

     編輯:MySQL 資料庫相關知識

遷移背景:業務系統數據庫平台替換

因成本、架構調整需將 SQL Server 數據庫全量遷移至 MySQL。調研兩條主流路徑:

  1. ODBC 直導:安裝 MySQL ODBC Driver → SQL Server 導出向導選擇 MySQL DSN → 圖形化映射導出
  2. 腳本遷移:PHP/Python/Shell 腳本讀取 SQL Server → 數據清洗/類型轉換 → 寫入 MySQL

方案一:ODBC 直導 —— 簡便但坑深

操作流程

  1. 安裝 MySQL ODBC 8.0 Unicode Driver (匹配 MySQL 版本)
  2. ODBC 管理員創建 System DSN:MySQL_DSN,指向目標 MySQL 實例
  3. SQL Server Management Studio → 任務 → 導出數據 → 數據源選 MySQL_DSN → 選表/視圖 → 運行

核心痛點與失敗案例

問題現象根因影響
大對象類型不兼容ntext、image、xml 導出失敗或截斷ODBC 驅動/導出向導對 LOB 類型映射缺陷業務關鍵文檔/圖片/日志數據丟失
時間類型衝突SQL Server datetime → MySQL INT (時間戳) 導入報錯/數值錯誤類型映射邏輯固化,無法自定義轉換函數所有時間字段遷移失效
目標表結構不受控自動建表生成 VARCHAR(MAX)、DATETIME 等非預期類型向導根據源類型推斷,不符合 MySQL 最佳實踐 (utf8mb4, InnoDB, 索引優化)上線後性能差、存儲浪費、需重構
編碼亂碼中文欄位變「???」或亂碼ODBC DSN 未正確設置 charset=utf8mb4、SQL Server 列排序規則衝突數據不可用,需重導
無法斷點續傳大表導入中斷需全量重來向導無事務/批次控制TB 級數據遷移風險極高

結論:僅適用於**小數據量、結構簡單、類型標準、容錯率高**的測試/開發環境遷移。生產環境**強烈不推薦**。

方案二:PHP 腳本遷移 —— 可控、可擴展、生產級

核心優勢

  • 完全掌握類型映射:datetime → UNIX_TIMESTAMP()、ntext → LONGTEXT、bit → TINYINT(1)、uniqueidentifier → CHAR(36)
  • 數據清洗內建:去重、修臉、格式統一、敏感字段脫敏、分區鍵計算
  • 批次事務控制:每 1000/5000 行提交,失敗重試、斷點續傳、進度監控
  • 目標表預建優化:預先建好 ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED、分區表、覆蓋索引
  • 並行遷移:多進程/協程按主鍵範圍分片並發,大幅縮短窗口期

PHP 遷移腳本核心骨架 (PDO + 生成器 + 批次提交)


 PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false  // 流式讀取大結果集
]);

// 1. 獲取源表列信息 (含類型、長度、精度、標度)
$cols = $src->query("
    SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE TABLE_NAME = 'users' AND TABLE_SCHEMA = 'dbo'
")->fetchAll(PDO::FETCH_ASSOC);

// 2. 構建目標表 DDL (預建優化版)
$ddl = "CREATE TABLE IF NOT EXISTS `users` (\n";
foreach ($cols as $c) {
    $mysql_type = map_type($c['DATA_TYPE'], $c['CHARACTER_MAXIMUM_LENGTH'], $c['NUMERIC_PRECISION'], $c['NUMERIC_SCALE']);
    $ddl .= "  `{$c['COLUMN_NAME']}` $mysql_type,\n";
}
$ddl .= "  PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED;";
$dst->exec($ddl);

// 3. 流式讀取 + 批次寫入
$select = $src->prepare("SELECT * FROM dbo.users ORDER BY id");
$select->execute();

$insert_sql = "INSERT INTO `users` (" . implode(',', array_map(fn($c)=>"`{$c['COLUMN_NAME']}`", $cols)) . ") VALUES (" . str_repeat('?,', count($cols)-1) . "?)";
$ins = $dst->prepare($insert_sql);

$batch = [];
$batch_size = 2000;
$total = 0;

while ($row = $select->fetch(PDO::FETCH_ASSOC)) {
    // 數據轉換/清洗
    foreach ($row as $k => $v) {
        if ($v instanceof DateTime) {
            $row[$k] = $v->getTimestamp();  // datetime → timestamp
        } elseif (is_resource($v) && get_resource_type($v) === 'stream') {
            $row[$k] = stream_get_contents($v);  // ntext/image → 文本/二進制
        } elseif ($v === null) {
            $row[$k] = null;
        }
    }
    $batch[] = array_values($row);
    
    if (count($batch) >= $batch_size) {
        $dst->beginTransaction();
        foreach ($batch as $vals) $ins->execute($vals);
        $dst->commit();
        $total += count($batch);
        $batch = [];
        echo "已遷移 $total 行\n";
    }
}

// 剩餘批次
if ($batch) {
    $dst->beginTransaction();
    foreach ($batch as $vals) $ins->execute($vals);
    $dst->commit();
    $total += count($batch);
}
echo "遷移完成,共 $total 行。";

// 類型映射函數 (簡化版)
function map_type($sql_type, $len, $prec, $scale) {
    static $map = [
        'int' => 'INT', 'bigint' => 'BIGINT', 'smallint' => 'SMALLINT', 'tinyint' => 'TINYINT',
        'bit' => 'TINYINT(1)',
        'decimal' => "DECIMAL($prec,$scale)", 'numeric' => "DECIMAL($prec,$scale)",
        'money' => 'DECIMAL(19,4)', 'smallmoney' => 'DECIMAL(10,4)',
        'float' => 'DOUBLE', 'real' => 'FLOAT',
        'datetime' => 'BIGINT', 'datetime2' => 'BIGINT', 'smalldatetime' => 'BIGINT', 'date' => 'DATE', 'time' => 'TIME',
        'char' => "CHAR($len)", 'nchar' => "CHAR($len)",
        'varchar' => "VARCHAR($len)", 'nvarchar' => "VARCHAR($len)",
        'text' => 'TEXT', 'ntext' => 'LONGTEXT',
        'image' => 'LONGBLOB',
        'uniqueidentifier' => 'CHAR(36)',
        'xml' => 'LONGTEXT',
    ];
    return $map[strtolower($sql_type)] ?? 'LONGTEXT';
}

生產環境遷移清單

階段關鍵動作驗收標準
準備目標庫預建表、索引、分區;凍結 Schema 變更DDL 執行無錯誤,SHOW CREATE TABLE 符合預期
全量遷移腳本跑批、斷點續傳、進度監控、錯誤重試、數據校驗 (行數、MD5、抽樣對比)全表行數一致,抽樣 1000 行字段值逐個一致
增量同步CDC (Debezium/Maxwell/Canal) 或觸發器/時間戳輪詢捕獲增量,應用至目標延遲 < 5 分鐘,無數據丟失/重複
雙寫驗證應用層雙寫 / 讀流量鏡像 / Shadow Table 對比連續 7 天零差異
切換上線停寫源庫 → 最後一增量 → DNS/配置切換 → 觀察監控業務零感知,回滾預案就緒

結語

異構遷移的本質是 **「數據類型映射 + 業務語義保持 + 運維可控性」**。ODBC 直導看似省力,實則將最關鍵的類型轉換、清洗、錯誤處理交給了不透明的向導,風險不可控。投入精力編寫/複用遷移腳本框架,才是專業團隊的選擇。記住:**預建目標表、流式讀寫、批次事務、校驗對比、增量同步、雙寫驗證**——這六步走穩,遷移才不會成「遷坟」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved