最直接方式是Db::execute()配合手写INSERT INTO...VALUES(),(),()语句,支持IGNORE、ON DUPLICATE KEY UPDATE,需手动分片与事务处理,适用于精细控制场景。

ThinkPHP 中用原生 SQL 批量插入,最直接的方式是调用 Db::execute() 配合手写 INSERT INTO ... VALUES (),(),() 语句,适用于需要精细控制(如跳过重复、更新冲突、自定义字段顺序)的场景。
手写 INSERT VALUES 多值语句
这是最贴近“原生”的做法,完全由你拼 SQL 和参数,框架只负责执行:
- SQL 模板固定为
INSERT INTO table (col1,col2) VALUES (?,?,?),(?,?,?)—— 注意括号数量与每行字段数一致 - 参数必须一维扁平排列:比如插入 2 行、每行 3 字段,就要传 6 个值的数组
- 示例:
<?php
use think\Db;
$sql = 'INSERT INTO user (name, email, status) VALUES (?, ?, ?), (?, ?, ?)';
$params = ['张三', 'z3@example.com', 1, '李四', 'l4@example.com', 0];
$affected = Db::execute($sql, $params);
// 返回影响行数,如 2
?>
支持 IGNORE 或 ON DUPLICATE KEY UPDATE
insertAll() 不支持这些语法,但原生 SQL 可以直接写:
立即学习“PHP免费学习笔记(深入)”;
- 跳过重复(MySQL):
INSERT IGNORE INTO user (...) VALUES (...) - 存在则更新:
INSERT INTO user (...) VALUES (...) ON DUPLICATE KEY UPDATE status = VALUES(status), updated_at = NOW() - 注意:VALUES(col) 表示该次插入中对应列的原始值,不是函数
大批量时手动分片 + 事务
单条 SQL 过长会触发 max_allowed_packet 限制(默认 4MB),需主动切分:
- 用
array_chunk($data, 300)把原始二维数组按每批 300 行切分 - 对每批数据生成对应长度的
(?, ?, ?)占位符串,并展平参数 - 用
Db::transaction()包裹整个循环,确保全成功或全回滚 - 示例逻辑片段:
$chunks = array_chunk($data, 300);
Db::transaction(function () use ($chunks) {
foreach ($chunks as $chunk) {
$placeholders = str_repeat('(?, ?, ?),', count($chunk) - 1) . '(?, ?, ?)';
$sql = "INSERT INTO user (name, email, status) VALUES {$placeholders}";
$params = [];
foreach ($chunk as $row) {
$params[] = $row['name'];
$params[] = $row['email'];
$params[] = $row['status'];
}
Db::execute($sql, $params);
}
});
为什么不用 insertAll() 而选原生 SQL
当遇到以下情况时,原生 SQL 是更合适的选择:
- 必须使用
INSERT IGNORE或ON DUPLICATE KEY UPDATE - 表结构和输入数组字段名不一致,且不想改 strict 模式
- 需要混合不同字段顺序,或动态决定是否包含某些列
- 想在 SQL 层统一写入时间戳(如
NOW()),而非 PHP 侧生成 - 调试时需要看到完整可执行语句(含真实值),避免 PDO 占位符干扰



















