循環調用 ActiveRecord::save() 執行 N 條 INSERT 是性能殺手。Yii2 提供 yii\db\Command::batchInsert() 直接構建批量 INSERT 語句,單次 SQL 寫入成百上千條記錄,性能提升 10-50 倍。本文詳解 batchInsert 用法、AR 模型驗證結合、分批提交、事務控制與避坑指南。
<?php
// 目標表:test (id PK, name VARCHAR, status TINYINT, created_at INT)
// 1. 準備列名 (不含自增 id)
$columns = ['name', 'status', 'created_at'];
// 2. 準備行數據 (二維數組,順序對應 $columns)
$rows = [
['張三', 1, time()],
['李四', 1, time()],
['王五', 0, time()],
// ... 成百上千行
];
// 3. 執行批量插入
$affected = Yii::$app->db->createCommand()
->batchInsert('test', $columns, $rows)
->execute();
echo "成功插入 $affected 行";
直接寫入繞過 AR 驗證有風險。正確模式:模型實例化 → validate() → 提取屬性 → batchInsert。
<?php
use yii\helpers\ArrayHelper;
// 假設 $models 是 Post 模型實例數組 (來自表單批量提交、Excel 導入等)
$validRows = [];
foreach ($models as $model) {
// 1. 執行驗證規則 (required, string, integer, unique 等)
if ($model->validate()) {
// 2. 提取屬性 (只取表字段,過濾臨時字段)
$validRows[] = $model->getAttributes(['title', 'content', 'author_id', 'status']);
} else {
// 記錄驗證失敗詳情
$errors[] = ['model' => $model, 'errors' => $model->getErrors()];
}
}
if (!empty($validRows)) {
// 3. 批量寫入 (使用模型 tableName() 與 attributes() 保持一致)
$columns = array_keys($validRows[0]); // ['title', 'content', 'author_id', 'status']
Yii::$app->db->createCommand()
->batchInsert(Post::tableName(), $columns, $validRows)
->execute();
}
// 處理錯誤反饋
if (!empty($errors)) {
// 返回前端顯示具體哪行哪字段驗證失敗
}
<?php
function batchInsertChunked(string $table, array $columns, array $rows, int $batchSize = 500): int
{
$totalInserted = 0;
$db = Yii::$app->db;
// 可選:開啟事務 (視業務是否要求原子性)
$transaction = $db->beginTransaction();
try {
foreach (array_chunk($rows, $batchSize) as $chunk) {
$db->createCommand()
->batchInsert($table, $columns, $chunk)
->execute();
$totalInserted += count($chunk);
}
$transaction->commit();
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
}
return $totalInserted;
}
// 使用
$rows = [...] ; // 10000 行
$inserted = batchInsertChunked('test', ['name', 'status'], $rows, 1000);
MySQL:batchInsert 返回影響行數,無法直接獲取自增 ID 序列。方案:
INSERT ... RETURNING id,Yii2 尚未原生封裝,需手寫 SQL。// PostgreSQL 原生支持 RETURNING
$sql = "INSERT INTO test (name, status) VALUES " .
implode(',', array_fill(0, count($rows), '(?,?)')) .
" RETURNING id";
$ids = Yii::$app->db->createCommand($sql, array_merge(...$rows))->queryColumn();
Yii2 batchInsert 不直接支持 ON DUPLICATE KEY UPDATE。需手構建 SQL 或使用 yii\db\mysql\QueryBuilder::buildBatchInsert() 獲取基礎 SQL 後拼接:
$command = Yii::$app->db->createCommand(); $sql = $command->getSql() . ' ON DUPLICATE KEY UPDATE `status`=VALUES(`status`), `updated_at`=VALUES(`updated_at`)'; // 注意:getSql() 為 protected,需反射或複製 QueryBuilder 邏輯 // 更簡單:手寫完整 SQL 執行
| 方式 | 1000 條耗時(估) | SQL 條數 | 驗證 | 適用場景 |
|---|---|---|---|---|
| AR save() 循環 | 3-10 秒 | 1000 | 自動 | 極少量、需事件/行為 |
| AR save(false) 循環 | 1-3 秒 | 1000 | 跳過 | 不推薦 |
| batchInsert 一次性 | 0.05-0.2 秒 | 1 | 需手動 | 中等量、無併發衝突 |
| batchInsert 分批 + 事務 | 0.2-0.5 秒 | N/批次 | 需手動 | 大量、生產環境推薦 |
| LOAD DATA INFILE | 0.01-0.05 秒 | 1 | 無 | 極大量導入、文件直導 |
$columns 與 $rows 每行元素順序必須完全一致。$columns 不要包含主鍵列。array_chunk 或生成器分批構建。Yii2 batchInsert 是批量寫入的「核武器」:單條 SQL、參數綁定安全、性能極致。生產環境鐵律:模型驗證先行 → 提取純屬性數組 → 分批事務提交 → 異常回滾記錄。結合 array_chunk、預生成 ID、RETURNING 新特性,可優雅解決從導入 Excel 到日志归档的各類批量寫入需求。