YII2批量插入数据方法
在 Yii2 中批量添加数据(批量插入 MySQL),核心是利用 ActiveRecord 的 batchInsert() 方法或 Query 构建器的 batchInsert(),避免循环调用 save() 导致的性能问题(减少数据库连接次数,一次 SQL 插入多条数据)。
以下是 3 种实用方案(按推荐优先级排序),覆盖不同场景(简单批量插入、带验证、关联数据批量插入),并兼容你的 Yii2 2.0.53 版本:
一、核心前提
假设模型为 User(对应表 sp_user),需批量插入的字段为 name、age、status,待插入数据格式如下:
// 原始批量数据(可含多余字段,后续会过滤)
$batchData = [
['name' => '张三', 'age' => 20, 'status' => 1, 'extra' => '无效字段'],
['name' => '李四', 'age' => 22, 'status' => 1, 'extra' => '无效字段'],
['name' => '王五', 'age' => 25, 'status' => 0, 'extra' => '无效字段'],
];方案 1:ActiveRecord::batchInsert()(最简洁,推荐)
Yii2 ActiveRecord 静态方法 batchInsert(),直接构造批量插入 SQL,性能最优(一次 SQL 执行),支持自动过滤无效字段。
用法示例:
use app\models\User;
// 步骤 1:定义允许插入的字段(避免无效字段报错)
$allowedFields = ['name', 'age', 'status'];
// 步骤 2:过滤批量数据中的无效字段(仅保留允许的字段)
$validBatchData = [];
foreach ($batchData as $item) {
// 仅保留 $allowedFields 中的字段,自动过滤 extra 等无效字段
$validItem = array_intersect_key($item, array_flip($allowedFields));
$validBatchData[] = $validItem;
}
// 步骤 3:批量插入(核心方法)
$rowCount = User::batchInsert(
$allowedFields, // 允许插入的字段(必须与表字段一致)
$validBatchData // 过滤后的批量数据
)->execute();
// 结果:$rowCount 为成功插入的行数(此处为 3)
echo "批量插入成功,共插入 {$rowCount} 条数据";生成的 SQL(性能最优):
INSERT INTO `sp_user` (`name`, `age`, `status`)
VALUES ('张三', 20, 1), ('李四', 22, 1), ('王五', 25, 0);特点:
- 性能最好(一次 SQL 插入多条,无循环连接数据库);
- 需手动指定允许插入的字段(避免无效字段);
- 不触发模型的
beforeSave()/afterSave()钩子,不执行数据验证(需提前确保数据合法)。
方案 2:带数据验证的批量插入(安全优先)
若需对批量数据进行验证(如 name 必填、age 为整数),需循环验证每条数据,再批量插入,兼顾安全与性能。
用法示例:
use app\models\User;
use yii\db\Exception;
$allowedFields = ['name', 'age', 'status'];
$validBatchData = [];
// 步骤 1:循环验证每条数据(触发模型规则)
foreach ($batchData as $item) {
$model = new User();
// 加载数据并过滤无效字段
$model->load(['User' => $item], '');
// 验证数据(触发模型 rules() 中的规则)
if ($model->validate()) {
// 仅保留允许插入的字段,加入有效数据集合
$validItem = $model->getAttributes($allowedFields);
$validBatchData[] = $validItem;
} else {
// 处理验证失败的情况(如打印错误信息)
echo "数据验证失败:" . json_encode($model->errors) . "\n";
}
}
// 步骤 2:批量插入验证通过的数据
if (!empty($validBatchData)) {
try {
$rowCount = User::batchInsert($allowedFields, $validBatchData)->execute();
echo "批量插入成功,共插入 {$rowCount} 条数据";
} catch (Exception $e) {
echo "插入失败:" . $e->getMessage();
}
}特点:
- 触发模型验证(
rules())和属性过滤(getAttributes()),数据更安全; - 验证失败的数据会被跳过,不影响整体插入;
- 性能略低于方案 1(多了循环验证步骤),但仍优于循环
save()。
方案 3:Query 构建器批量插入(兼容非 ActiveRecord 场景)
若不依赖 ActiveRecord 模型(如临时表、视图),可直接用 Query 构建器的 batchInsert(),用法与方案 1 类似,但更灵活。
用法示例:
use yii\db\Query;
use yii\db\Exception;
$allowedFields = ['name', 'age', 'status'];
$validBatchData = [];
// 过滤无效字段(同方案 1)
foreach ($batchData as $item) {
$validItem = array_intersect_key($item, array_flip($allowedFields));
$validBatchData[] = $validItem;
}
// 批量插入(指定表名和字段)
try {
$rowCount = (new Query())
->insert('sp_user', $allowedFields) // 表名 + 字段
->batchInsert($validBatchData) // 批量数据
->execute();
echo "批量插入成功,共插入 {$rowCount} 条数据";
} catch (Exception $e) {
echo "插入失败:" . $e->getMessage();
}特点:
- 不依赖
ActiveRecord模型,直接操作表名; - 语法简洁,性能与方案 1 一致;
- 同样不触发模型钩子和验证,需手动确保数据合法。
二、关键优化与避坑要点
1. 性能优化:控制单次插入数量
MySQL 对单次插入的条数有默认限制(取决于 max_allowed_packet 配置),若批量数据超过 1000 条,建议分批次插入(避免 SQL 过长导致失败):
$batchSize = 1000; // 每批插入 1000 条
$totalData = $validBatchData;
$totalCount = count($totalData);
for ($i = 0; $i < $totalCount; $i += $batchSize) {
// 截取每批数据
$batch = array_slice($totalData, $i, $batchSize);
User::batchInsert($allowedFields, $batch)->execute();
}2. 自动过滤无效字段(简化写法)
若不想手动定义 $allowedFields,可通过模型的 attributes() 自动获取所有字段(对应数据库表字段):
// 自动获取模型所有有效字段(无需手动写 $allowedFields)
$allowedFields = $model->attributes(); // 或 User::getTableSchema()->columnNames;
// 过滤批量数据
foreach ($batchData as $item) {
$validItem = array_intersect_key($item, array_flip($allowedFields));
$validBatchData[] = $validItem;
}3. 处理自增 ID
若表有自增 ID(如 id),无需在 $allowedFields 中包含 id,MySQL 会自动生成自增 ID。
4. 事务支持(确保数据一致性)
若批量插入需保证「要么全成功,要么全失败」,需包裹事务:
use yii\db\Transaction;
$db = User::getDb();
$transaction = $db->beginTransaction(Transaction::READ_COMMITTED);
try {
// 批量插入
$rowCount = User::batchInsert($allowedFields, $validBatchData)->execute();
$transaction->commit(); // 全部成功,提交事务
echo "批量插入成功,共插入 {$rowCount} 条数据";
} catch (Exception $e) {
$transaction->rollBack(); // 部分失败,回滚事务
echo "插入失败:" . $e->getMessage();
}5. 兼容 JSON 字段(若有)
若批量插入包含 JSON 字段(如 profile),需确保数据为数组或 JSON 字符串,Yii2 会自动处理:
$batchData = [
['name' => '张三', 'profile' => ['age' => 20, 'city' => '北京']], // 数组格式
['name' => '李四', 'profile' => json_encode(['age' => 22, 'city' => '上海'])], // JSON 字符串格式
];
// 直接批量插入,Yii2 会自动将数组转为 JSON 字符串
User::batchInsert(['name', 'profile'], $batchData)->execute();三、场景对比与选择
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 方案 1(ActiveRecord::batchInsert) | 性能最优,简洁 | 无验证,无钩子 | 数据已验证、无需触发模型逻辑 |
| 方案 2(带验证) | 数据安全,支持验证 | 性能略低 | 需验证数据(如用户输入数据) |
| 方案 3(Query 构建器) | 不依赖 ActiveRecord | 无模型相关功能 | 临时表、视图、非模型场景 |
推荐优先级:
- 大多数场景(数据合法、无需验证)→ 方案 1;
- 用户输入数据、需验证 → 方案 2;
- 非 ActiveRecord 场景 → 方案 3。
四、最终简化写法(方案 1 + 自动过滤字段 + 事务)
use app\models\User;
use yii\db\Exception;
use yii\db\Transaction;
$batchData = [/* 你的批量数据 */];
$model = new User();
$allowedFields = $model->attributes(); // 自动获取所有有效字段
$validBatchData = [];
// 自动过滤无效字段
foreach ($batchData as $item) {
$validItem = array_intersect_key($item, array_flip($allowedFields));
$validBatchData[] = $validItem;
}
// 事务 + 批量插入
$db = User::getDb();
$transaction = $db->beginTransaction(Transaction::READ_COMMITTED);
try {
if (!empty($validBatchData)) {
$rowCount = User::batchInsert($allowedFields, $validBatchData)->execute();
}
$transaction->commit();
echo "批量插入成功,共插入 {$rowCount} 条数据";
} catch (Exception $e) {
$transaction->rollBack();
echo "插入失败:" . $e->getMessage();
}这种写法兼顾了 性能、安全性、简洁性,能自动过滤无效字段,避免「字段不存在」报错,同时通过事务保证数据一致性,适合绝大多数批量添加场景。
版权属于:Joyber
本文链接:https://blog.qqvbc.com/default/1396.html
转载时须注明出处及本声明