每日需從文字檔匯入 1.5 萬筆電話號碼至 MySQL。原邏輯逐筆 INSERT,耗時 30 分鐘以上,嚴重影響排程效率。
<?php
// $array 為 explode 後的號碼陣列,$type 為分類
for ($i = 0; $i < count($array); $i++) {
$sql = "INSERT INTO {$table} (tel, type) VALUES ('{$array[$i]}', '{$type}')";
mysql_query($sql); // 每筆一個 round-trip
}
// 15,000 筆 ≈ 30 分鐘
<?php
$sql = "INSERT INTO {$table} (tel, type, updatetime) VALUES ";
$values = [];
foreach ($array as $tel) {
// 使用預處理參數化更安全,此處為示範字串拼接
$values[] = "('" . mysql_real_escape_string($tel) . "', '{$type}', NOW())";
}
$sql .= implode(',', $values);
mysql_query($sql); // 單次 round-trip
// 15,000 筆 ≈ 10 秒
<?php
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, // 非緩衝查詢,省記憶體
]);
$batchSize = 1000; // 每批 1000 筆
$stmt = $pdo->prepare("INSERT INTO {$table} (tel, type, updatetime) VALUES (?, ?, NOW())");
$pdo->beginTransaction();
try {
$count = 0;
foreach ($array as $tel) {
$stmt->execute([$tel, $type]);
if (++$count % $batchSize === 0) {
$pdo->commit();
$pdo->beginTransaction(); // 重新開啟交易,避免長交易鎖表
}
}
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
| 優化項目 | 原狀況 | 優化後 | 效果 |
|---|---|---|---|
| SQL 執行次數 | 15,000 次 | 1 次 (或分批) | 網路往返從 30 分鐘降至毫秒級 |
| 交易控制 | 自動提交 | 手動批次交易 | 減少索引更新、鎖開銷 |
| 緩衝模式 | Buffered Query | Unbuffered Query | 記憶體從 O(N) 降為 O(1) |
| 索引策略 | 無索引或過多索引 | 匯入前刪除非唯一索引,匯入後重建 | 寫入速度提升 3-5 倍 |
| LOAD DATA INFILE | 未使用 | 適用純文字檔 | 最快,繞過 SQL 層解析 |
<?php
// 將陣列寫入暫存檔
$tmpFile = sys_get_temp_dir() . '/import_' . uniqid() . '.csv';
$fp = fopen($tmpFile, 'w');
foreach ($array as $tel) {
fputcsv($fp, [$tel, $type, date('Y-m-d H:i:s')]);
}
fclose($fp);
$sql = "LOAD DATA LOCAL INFILE '{$tmpFile}'
INTO TABLE {$table}
FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\n'
(tel, type, updatetime)";
$pdo->exec($sql);
unlink($tmpFile);
// 15,000 筆 ≈ 1-2 秒
前提:MySQL 啟用 local_infile=1,PHP PDO 設定 PDO::MYSQL_ATTR_LOCAL_INFILE => true,檔案路徑對 MySQL Server 可讀。
INSERT IGNORE、ON DUPLICATE KEY UPDATE 或 REPLACE INTO