本文詳細說明如何使用 PHP 內建函數 fgetcsv() 解析 CSV 檔案,並將資料批次匯入 MySQL 資料庫。涵蓋檔案上傳、編碼處理、資料驗證、錯誤處理、進度回饋等完整流程,適合會員匯入、商品批次更新、報表資料導入等場景。
enctype="multipart/form-data")fopen() + fgetcsv() 逐行讀取解析10 * 1024 * 1024) { // 10MB 限制 $errors[] = '檔案過大,請勿超過 10MB'; } $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if ($ext !== 'csv') { $errors[] = '僅支援 .csv 副檔名'; } if (!empty($errors)) { die(''); } // 移動暫存檔到安全位置 $uploadDir = sys_get_temp_dir() . '/csv_imports/'; if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true); $tmpName = $uploadDir . uniqid('csv_') . '.csv'; if (!move_uploaded_file($file['tmp_name'], $tmpName)) { die('無法儲存上傳檔案'); } // ===== 2. 資料庫連線 (PDO + 例外模式) ===== try { $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=$charset", $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, // 使用真實 Prepared Statements ]); } catch (PDOException $e) { @unlink($tmpName); die('資料庫連線失敗: ' . $e->getMessage()); } // ===== 3. 開啟 CSV 檔案 (自動偵測編碼) ===== $handle = fopen($tmpName, 'r'); if (!$handle) { @unlink($tmpName); die('無法開啟 CSV 檔案'); } // 嘗試偵測 BOM 與編碼 $firstLine = fgets($handle, 4096); rewind($handle); // 簡易 BOM 檢測 $bom = ''; if (strncmp($firstLine, "\xEF\xBB\xBF", 3) === 0) { $bom = 'UTF-8'; } elseif (strncmp($firstLine, "\xFF\xFE", 2) === 0 || strncmp($firstLine, "\xFE\xFF", 2) === 0) { $bom = 'UTF-16'; } // ===== 4. 讀取標題列 (對應欄位) ===== $headers = fgetcsv($handle, 4096, ',', '"', '\\'); if (!$headers) { fclose($handle); @unlink($tmpName); die('CSV 檔案為空或格式錯誤'); } // 正規化標題 (去除 BOM、空白、特殊字元) $headers = array_map(function($h) { $h = trim($h); $h = preg_replace('/^\xEF\xBB\xBF/', '', $h); // 移除 UTF-8 BOM return $h; }, $headers); echo "
- ' . implode('
- ', $errors) . '
偵測到欄位: " . implode(', ', $headers) . "
"; // ===== 5. 準備 Prepared Statement ===== // 假設資料表欄位: name, classes, a_time, college, notify, receiver, r_time, handler $placeholders = implode(',', array_fill(0, count($headers), '?')); $columns = implode(',', array_map(function($c) { return "`$c`"; }, $headers)); $sql = "INSERT INTO `$targetTable` ($columns) VALUES ($placeholders)"; $stmt = $pdo->prepare($sql); // ===== 6. 批次匯入主迴圈 ===== $pdo->beginTransaction(); $success = 0; $failed = 0; $errors = []; $rowNum = 1; // 已讀標題列,資料從第 2 列開始 while (($data = fgetcsv($handle, 4096, ',', '"', '\\')) !== FALSE) { $rowNum++; // 跳過空行 if (count(array_filter($data)) === 0) continue; // 欄位數驗證 if (count($data) !== count($headers)) { $failed++; $errors[] = "第 $rowNum 行: 欄位數不符 (預期 " . count($headers) . ", 實際 " . count($data) . ")"; if ($failed > 100) break; // 錯誤過多停止 continue; } // 編碼轉換 (若 CSV 非 UTF-8) // $data = array_map(function($v) { return iconv('BIG5', 'UTF-8//IGNORE', $v); }, $data); try { $stmt->execute($data); $success++; // 批次提交 if ($success % $batchSize === 0) { $pdo->commit(); $pdo->beginTransaction(); echo ""; ob_flush(); flush(); } } catch (PDOException $e) { $failed++; $errors[] = "第 $rowNum 行: " . $e->getMessage(); } } // 提交剩餘交易 $pdo->commit(); fclose($handle); @unlink($tmpName); // ===== 7. 結果報告 ===== echo ""; echo ""; exit; } ?>匯入完成
"; echo "成功: $success 筆
"; echo "失敗: $failed 筆
"; echo "總計: " . ($success + $failed) . " 筆
"; if (!empty($errors)) { echo ""; } echo "錯誤明細 (前 50 筆)
"; foreach (array_slice($errors, 0, 50) as $err) { echo "
- " . htmlspecialchars($err) . "
"; } echo "
CSV 匯入系統 CSV 資料匯入
檔案需求
- 格式: UTF-8 編碼 CSV (逗號分隔)
- 第一列必須為標題列,欄位名稱對應資料表欄位
- 支援欄位: name, classes, a_time, college, notify, receiver, r_time, handler
- 檔案大小限制: 10MB
- 日期格式建議: YYYY-MM-DD HH:MM:SS
name,classes,a_time,college,notify,receiver,r_time,handler 張三,一年甲班,2024-01-15 08:30:00,資訊學院,系統通知,李老師,2024-01-15 09:00:00,教務處 李四,二年乙班,2024-01-16 10:00:00,管理學院,郵件通知,王主任,2024-01-16 10:30:00,學務處 王五,三年丙班,2024-01-17 14:00:00,工學院,簡訊通知,趙導師,2024-01-17 14:30:00,總務處
| 場景 | 優化策略 |
|---|---|
| 大量資料 (>10萬筆) | 使用 LOAD DATA INFILE SQL 指令,比 PHP 逐行插入快 10-50 倍 |
| 記憶體不足 | 分批讀取、分批提交交易、避免一次載入整個檔案 |
| 頻繁匯入 | 建立匯入佇列 + 背景 Worker (Gearman/Redis Queue/Laravel Jobs) |
| 需更新而非新增 | 使用 INSERT ... ON DUPLICATE KEY UPDATE 或 REPLACE INTO |
iconv('BIG5', 'UTF-8//IGNORE', $v) 轉碼。YYYY-MM-DD HH:MM:SS。CSV 中若為其他格式需先轉換,或在 SQL 中使用 STR_TO_DATE()。fgetcsv() 可正確解析。確保 CSV 產生工具符合 RFC 4180 標準。INSERT ... ON DUPLICATE KEY UPDATE 更新現有記錄,或匯入前先檢查。