面對百萬級甚至億級資料匯入,單條 INSERT 效能極差。本文系統整理 MySQL 官方推薦的優化策略:多值 INSERT、LOAD DATA INFILE、索引延遲建立、INSERT DELAYED、鎖控制與事務批次,並提供實測參考數據。
| 策略 | 適用場景 | 相對速度 (參考) | 注意事項 |
|---|---|---|---|
| 單條 INSERT | 極少量、即時業務 | 1x (基準) | 每條皆需解析、鎖、刷盤 |
| 多值 INSERT (批次) | 應用層組裝,萬級以內 | 5-20x | 單條 SQL 長度受 max_allowed_packet 限制 |
| LOAD DATA INFILE | 文本檔案匯入、初始化、ETL | 20-100x | 需 FILE 權限、伺服器本地檔案、格式固定 |
| LOAD DATA + 索引延遲 | 大量匯入空表/重建表 | 50-200x | MyISAM 效果最顯著,InnoDB 需配合 |
| INSERT DELAYED | 日誌、統計、非即時業務 | 非阻塞 | MySQL 8.0 移除,僅 MyISAM/MEMORY 支援 |
<?php
// PHP PDO 批次插入範例
function batchInsert(PDO $pdo, string $table, array $columns, array $rows, int $batchSize = 1000): int
{
$placeholders = '(' . implode(',', array_fill(0, count($columns), '?')) . ')';
$columnList = '`' . implode('`,`', $columns) . '`';
$sql = "INSERT INTO `{$table}` ({$columnList}) VALUES ";
$total = 0;
$pdo->beginTransaction();
try {
foreach (array_chunk($rows, $batchSize) as $chunk) {
$valuesSql = implode(',', array_fill(0, count($chunk), $placeholders));
$stmt = $pdo->prepare($sql . $valuesSql);
$paramIndex = 1;
foreach ($chunk as $row) {
foreach ($row as $value) {
$stmt->bindValue($paramIndex++, $value);
}
}
$stmt->execute();
$total += $stmt->rowCount();
}
$pdo->commit();
} catch (\Throwable $e) {
$pdo->rollBack();
throw $e;
}
return $total;
}
// 使用
$columns = ['username', 'email', 'created_at'];
$rows = [];
for ($i = 0; $i < 50000; $i++) {
$rows[] = ["user{$i}", "user{$i}@example.com", date('Y-m-d H:i:s')];
}
$inserted = batchInsert($pdo, 'users', $columns, $rows);
echo "批次插入 {$inserted} 筆完成\n";
-- CSV 檔案格式 (users.csv) -- username,email,created_at -- user1,user1@example.com,2024-01-01 00:00:00 -- user2,user2@example.com,2024-01-01 00:00:01 LOAD DATA INFILE '/var/lib/mysql-files/users.csv' INTO TABLE users CHARACTER SET utf8mb4 FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' ESCAPED BY '\\\\' LINES TERMINATED BY '\n' IGNORE 1 LINES (username, email, created_at);
必要設定:
secure_file_priv:限制匯入目錄,檔案必須置於此目錄下。FILE 權限:GRANT FILE ON *.* TO 'user'@'host';LOAD DATA LOCAL INFILE 需啟用 local_infile=1 (客戶端與伺服器)。-- 步驟 1:建立表結構 (不含索引)
CREATE TABLE big_table (
id INT AUTO_INCREMENT PRIMARY KEY,
data VARCHAR(255),
category_id INT,
created_at DATETIME
) ENGINE=MyISAM;
-- 步驟 2:停用索引 (MyISAM)
ALTER TABLE big_table DISABLE KEYS; -- 等同 myisamchk --keys-used=0
-- 步驟 3:大量匯入
LOAD DATA INFILE '...' INTO TABLE big_table ...;
-- 步驟 4:重建索引
ALTER TABLE big_table ENABLE KEYS; -- 等同 myisamchk -rq
-- 步驟 5:壓縮表 (唯讀歸檔)
-- myisampack big_table
InnoDB 替代方案:
CREATE INDEX。innodb_buffer_pool_size (如 70% 記憶體)、innodb_log_file_size (如 1GB)、innodb_flush_log_at_trx_commit = 2 (匯入期間)。innodb_bulk_load_mode (MySQL 8.0.30+) 自動優化。-- 表鎖 (MyISAM) 或事務 (InnoDB) LOCK TABLES big_table WRITE; INSERT INTO big_table ... VALUES (...), (...), ...; -- 批次 INSERT INTO big_table ... VALUES (...), (...), ...; UNLOCK TABLES; -- InnoDB 事務批次 START TRANSACTION; INSERT ...; -- 批次 1 INSERT ...; -- 批次 2 COMMIT; -- 僅提交時刷盤一次
關鍵差異:
LOCK TABLES 減少索引緩衝區刷盤次數 (每批次 1 次 vs 每條 1 次)。innodb_flush_log_at_trx_commit=2 可延遲至每秒刷盤 (風險:崩潰丟失 1 秒資料)。LOCK TABLES 分區級)。LOAD DATA (注意鎖衝突)。util.importTable() 內建並行匯入、進度條、錯誤處理。| 方法 | 耗時 | 備註 |
|---|---|---|
| 單條 INSERT (自動提交) | ~180 秒 | 基準 |
| 批次 INSERT (1000 條/批,事務) | ~8 秒 | 22x 提升 |
| LOAD DATA INFILE | ~3 秒 | 60x 提升 |
| LOAD DATA + 僅主鍵 + 事務優化 | ~2 秒 | 90x 提升 |
max_allowed_packet 設定過小導致多值 INSERT 截斷 (建議 64M+)。utf8mb4。YYYY-MM-DD HH:MM:SS 標準格式。INSERT IGNORE、ON DUPLICATE KEY UPDATE 或 REPLACE。SET FOREIGN_KEY_CHECKS=0; 結束後恢復。SET UNIQUE_CHECKS=0; (InnoDB 亦有效)。