CSV(逗號分隔值)是系統間資料交換最通用的格式。本文提供一個完整的 PHP + MySQL 導入/導出 CSV 實作,涵蓋編碼轉換(UTF-8 ↔ GB2312)、fgetcsv 解析、批量 INSERT 優化、HTTP 下載頭設定,並針對大數據量場景給出效能建議。
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`sex` varchar(10) NOT NULL,
`age` smallint(3) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
<form id="addform" action="do.php?action=import" method="post" enctype="multipart/form-data">
<p>請選擇要導入的 CSV 檔案:<br/>
<input type="file" name="file">
<input type="submit" class="btn" value="導入 CSV">
<input type="button" class="btn" value="導出 CSV" onclick="window.location.href='do.php?action=export'"></p>
</form>
<?php
include_once "connect.php"; // PDO 連線
$action = $_GET['action'] ?? '';
if ($action === 'import') {
// ---- 導入 CSV ----
$filename = $_FILES['file']['tmp_name'] ?? '';
if (!$filename) { exit('請選擇 CSV 檔案!'); }
$handle = fopen($filename, 'r');
if (!$handle) { exit('無法開啟檔案!'); }
$rows = [];
while (($data = fgetcsv($handle, 10000)) !== FALSE) {
$rows[] = $data;
}
fclose($handle);
if (count($rows) <= 1) { exit('無有效資料!'); }
// 跳過標題列,批量構建 VALUES
$values = [];
for ($i = 1; $i < count($rows); $i++) {
$name = iconv('GB2312', 'UTF-8', $rows[$i][0] ?? '');
$sex = iconv('GB2312', 'UTF-8', $rows[$i][1] ?? '');
$age = (int)($rows[$i][2] ?? 0);
$values[] = "('$name','$sex','$age')";
}
$sql = "INSERT INTO student (name, sex, age) VALUES " . implode(',', $values);
$affected = $pdo->exec($sql);
echo $affected ? "導入成功,共 $affected 筆" : "導入失敗:" . implode('; ', $pdo->errorInfo());
}
elseif ($action === 'export') {
// ---- 導出 CSV ----
$stmt = $pdo->query("SELECT name, sex, age FROM student ORDER BY id");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 標題列 + 內容,轉 GB2312 相容 Excel 中文
$output = fopen('php://temp', 'r+');
fputcsv($output, ['姓名', '性別', '年齡']); // 寫入標題
foreach ($rows as $row) {
fputcsv($output, [
iconv('UTF-8', 'GB2312//IGNORE', $row['name']),
iconv('UTF-8', 'GB2312//IGNORE', $row['sex']),
$row['age']
]);
}
rewind($output);
$csvContent = stream_get_contents($output);
fclose($output);
$filename = date('Ymd') . '_students.csv';
header('Content-Type: text/csv; charset=GB2312');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
echo $csvContent;
}
| 環節 | 要點 | 最佳實踐 |
|---|---|---|
| 編碼轉換 | 中文 Excel 偏好 GB2312/GBK | iconv('UTF-8', 'GB2312//IGNORE', $str) 導出;iconv('GB2312', 'UTF-8', $str) 導入。//IGNORE 忽略無法轉換字元。 |
| CSV 解析 | fgetcsv 自動處理引號、逗號、換行 | 設定足夠長度 fgetcsv($h, 10000);PHP 8+ 可用 fgetcsv($h, 0, ',', '"') 明確指定分隔符。 |
| 批量寫入 | 單次 INSERT ... VALUES (...),(...) | 每批 500-1000 列,避免 SQL 過長(max_allowed_packet)。大量資料分批提交事務。 |
| 記憶體控制 | 導出大量資料 | 使用 php://temp 暫存檔 + fputcsv 流式寫入,避免一次性佔用大量記憶體。 |
| 下載頭 | 觸發瀏覽器「另存為」 | Content-Disposition: attachment; filename="..." + Content-Type: text/csv;加 charset 避免瀏覽器誤判編碼。 |
LOAD DATA LOCAL INFILE 直接由 MySQL 讀取 CSV,速度比 PHP 批量 INSERT 快 10-50 倍。需確認 local_infile=1 且 PHP PDO 啟用 MYSQL_ATTR_LOCAL_INFILE。SELECT ... INTO OUTFILE '/tmp/export.csv' 由 MySQL 直接寫檔,再用 readfile() 或 Nginx X-Accel-Redirect 下發。LIMIT offset, chunk 迴圈,配合 set_time_limit(0)、ignore_user_abort(true)。fopen($f, 'r') 讀取時若檔案含 UTF-8 BOM,首欄會帶 \xEF\xBB\xBF。解決:fgets($h) 讀第一行檢測並移除 BOM,或 fseek($h, 3) 跳過。\t 或 ='value' 強制文本格式,或改用 Excel 專用 XML/HTML 格式匯出。fgetcsv 原生支援,無需特殊處理。掌握 fgetcsv/fputcsv + iconv 編碼轉換 + 批量 INSERT/流式輸出 + 正確 HTTP 頭,即可構建健壯的 CSV 導入導出功能。大數據場景請優先考慮 MySQL 原生 LOAD DATA/INTO OUTFILE 或非同步佇列架構。