首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 實現 MySQL 與 XML 雙向轉換:備份、遷移與數據交換完整方案

     編輯:PHP 相關知識

應用場景

  • 跨系統數據交換 (ERP ↔ CRM、老系統遷移)
  • 結構化備份 (可讀、可驗證、版本控制友好)
  • 離線分析、報表生成、數據歸檔

核心類:MySQL2XML (PDO 版、支援大表分批)

<?php
declare(strict_types=1);

class MySQL2XML {
    private PDO $pdo;
    private string $outputDir;
    private int $batchSize = 1000;
    private bool $includeSchema = true;
    private array $tables = [];
    
    public function __construct(PDO $pdo, string $outputDir = __DIR__ . '/xml_backups') {
        $this->pdo = $pdo;
        $this->outputDir = rtrim($outputDir, '/');
        if (!is_dir($this->outputDir)) mkdir($this->outputDir, 0755, true);
    }
    
    public function setTables(array $tables): self { $this->tables = $tables; return $this; }
    public function setBatchSize(int $size): self { $this->batchSize = $size; return $this; }
    public function setIncludeSchema(bool $flag): self { $this->includeSchema = $flag; return $this; }
    
    /** 執行導出 */
    public function export(): array {
        $tables = $this->tables ?: $this->getAllTables();
        $exported = [];
        
        foreach ($tables as $table) {
            $file = "{$this->outputDir}/{$table}.xml";
            $this->exportTable($table, $file);
            $exported[] = $file;
        }
        return $exported;
    }
    
    /** 導出單表 */
    private function exportTable(string $table, string $filepath): void {
        $xml = new XMLWriter();
        $xml->openUri($filepath);
        $xml->setIndent(true);
        $xml->setIndentString('  ');
        $xml->startDocument('1.0', 'UTF-8');
        $xml->startElement('table');
        $xml->writeAttribute('name', $table);
        $xml->writeAttribute('exported_at', date('c'));
        
        // 1. 結構
        if ($this->includeSchema) {
            $xml->startElement('structure');
            $cols = $this->pdo->query("SHOW COLUMNS FROM `{$table}`")->fetchAll(PDO::FETCH_ASSOC);
            foreach ($cols as $col) {
                $xml->startElement('column');
                foreach ($col as $k => $v) {
                    $xml->writeAttribute($k, (string)$v);
                }
                $xml->endElement(); // column
            }
            $xml->endElement(); // structure
        }
        
        // 2. 數據分批寫入
        $xml->startElement('data');
        $total = (int)$this->pdo->query("SELECT COUNT(*) FROM `{$table}`")->fetchColumn();
        $colNames = array_column($cols, 'Field');
        
        for ($offset = 0; $offset < $total; $offset += $this->batchSize) {
            $rows = $this->pdo->query(
                "SELECT * FROM `{$table}` LIMIT {$this->batchSize} OFFSET {$offset}"
            )->fetchAll(PDO::FETCH_ASSOC);
            
            foreach ($rows as $row) {
                $xml->startElement('row');
                foreach ($colNames as $col) {
                    $xml->startElement('field');
                    $xml->writeAttribute('name', $col);
                    $value = $row[$col];
                    if ($value === null) {
                        $xml->writeAttribute('null', 'true');
                    } else {
                        $xml->text((string)$value);
                    }
                    $xml->endElement(); // field
                }
                $xml->endElement(); // row
            }
        }
        $xml->endElement(); // data
        $xml->endElement(); // table
        $xml->flush();
    }
    
    private function getAllTables(): array {
        return $this->pdo->query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN);
    }
}

/** XML2MySQL 導入類 */
class XML2MySQL {
    private PDO $pdo;
    private int $batchSize = 500;
    
    public function __construct(PDO $pdo) { $this->pdo = $pdo; }
    public function setBatchSize(int $size): self { $this->batchSize = $size; return $this; }
    
    public function import(string $xmlFile, bool $truncateFirst = true): int {
        $reader = new XMLReader();
        $reader->open($xmlFile);
        
        $tableName = '';
        $inData = false;
        $rowCount = 0;
        $batch = [];
        $columns = [];
        
        // 先解析結構獲取表名與欄位
        while ($reader->read()) {
            if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'table') {
                $tableName = $reader->getAttribute('name');
                break;
            }
        }
        if (!$tableName) throw new \RuntimeException('Invalid XML: no table name');
        
        // 獲取欄位順序
        $reader->close();
        $reader->open($xmlFile);
        while ($reader->read()) {
            if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'column') {
                $columns[] = $reader->getAttribute('Field');
            }
            if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'data') {
                break;
            }
        }
        
        if ($truncateFirst) {
            $this->pdo->exec("TRUNCATE TABLE `{$tableName}`");
        }
        
        $placeholders = implode(',', array_fill(0, count($columns), '?'));
        $colList = '`' . implode('`,`', $columns) . '`';
        $sql = "INSERT INTO `{$tableName}` ({$colList}) VALUES ({$placeholders})";
        $stmt = $this->pdo->prepare($sql);
        
        $this->pdo->beginTransaction();
        while ($reader->read()) {
            if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'row') {
                $rowData = [];
                // 讀取 row 內容
                $reader->read(); // 移到第一個 field
                while ($reader->name !== 'row' || $reader->nodeType !== XMLReader::END_ELEMENT) {
                    if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === 'field') {
                        $isNull = $reader->getAttribute('null') === 'true';
                        $reader->read(); // 移到 text 節點
                        $value = $isNull ? null : $reader->value;
                        $rowData[] = $value;
                    }
                    $reader->read();
                }
                $batch[] = $rowData;
                $rowCount++;
                
                if (count($batch) >= $this->batchSize) {
                    foreach ($batch as $row) $stmt->execute($row);
                    $batch = [];
                }
            }
        }
        if ($batch) foreach ($batch as $row) $stmt->execute($row);
        $this->pdo->commit();
        $reader->close();
        
        return $rowCount;
    }
}

使用示例

<?php
// 導出
$pdo = new PDO('mysql:host=127.0.0.1;dbname=myapp', 'user', 'pass');
$exporter = new MySQL2XML($pdo, '/backups/xml');
$exporter->setTables(['users', 'orders', 'products'])
         ->setBatchSize(2000)
         ->export();
echo "導出完成\n";

// 導入
$importer = new XML2MySQL($pdo);
$count = $importer->import('/backups/xml/users.xml', true);
echo "導入 {$count} 筆\n";

生成 XML 結構示例

<?xml version="1.0" encoding="UTF-8"?>
<table name="users" exported_at="2024-01-15T14:30:00+08:00">
  <structure>
    <column Field="id" Type="int(11)" Null="NO" Key="PRI" Default="" Extra="auto_increment"/>
    <column Field="name" Type="varchar(100)" Null="NO" Key="" Default="" Extra=""/>
    <column Field="email" Type="varchar(150)" Null="YES" Key="UNI" Default="" Extra=""/>
  </structure>
  <data>
    <row>
      <field name="id">1</field>
      <field name="name">Alice</field>
      <field name="email">alice@example.com</field>
    </row>
    <row>
      <field name="id">2</field>
      <field name="name">Bob</field>
      <field name="email" null="true"/>
    </row>
  </data>
</table>

大數據量優化策略

  1. 分表導出:單表單獨文件,便於並行導入、增量同步。
  2. 壓縮傳輸:gzip users.xml 傳輸,導入時 gzopen 讀取。
  3. 並行導入:多進程分表導入,或使用 LOAD XML LOCAL INFILE (MySQL 8.0+)。
  4. 類型保真:XML 無原生類型,導入時需根據結構欄位類型轉換 (INT、DATE、DECIMAL 等)。

替代方案對比

選擇 XML 當您需要自描述、跨語言解析、結構驗證;選擇 SQL Dump 當您只需同構 MySQL 快速備份恢複。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
方案優點缺點適用
XML (本方案)可讀、自描述、標準化、支援層級結構體積大、解析慢、無原生類型數據交換、歸檔、小中量備份
JSON Lines體積較小、流式解析快、JS 原生無結構定義、大數字精度問題日誌、流數據、微服務通訊
CSV極小、Excel 可開、LOAD DATA 极快無類型、編碼易亂、無巢狀結構大數據導入導出、報表
SQL Dump原生類型、可直接執行、mysqldump 極快難解析、跨庫遷移需改寫同構備份恢複、版本升級