ThinkPHP批量写入必须用insertAll()或saveAll(),禁用循环;insertAll()高效无模型开销,适合纯插入;saveAll()支持验证与事件但性能略低;大批量须分片(≤500条)、事务包裹、预处理数据并防冲突。

ThinkPHP 接口接收数组后批量写入数据库,关键不是“能不能收”,而是“怎么安全高效地插”。核心就一条:别用循环 save 或 insert,必须走 insertAll() 或 saveAll(),否则 200 条以上大概率超时、锁表或被 WAF 拦。
确保接口传来的数组结构合规
前端发来的数据,比如 POST 的 JSON 或表单数组,必须转成「二维关联数组」才能被正确识别:
- 外层数组可以是数字索引或字符串键名,但每个子项必须是关联数组(
['name' => '张三', 'email' => 'z3@x.com']),不能是索引数组(['张三', 'z3@x.com'])或一维数组 - 所有子数组的键名要尽量一致;缺字段会导致 MySQL 报错
Column count doesn't match value count - 字段名大小写需与数据库列名严格一致(尤其在 Linux 环境下开启
lower_case_table_names=0时) - 如果是 JSON 提交,记得用
input('post.')或input('param.')获取并验证是否为数组:is_array($data) && is_array(reset($data))
选 insertAll 还是 saveAll?看你要不要模型逻辑
用 Db::name('table')->insertAll($data):直连数据库,拼一条 INSERT SQL,最快最稳。适合导入、同步类场景。
- 不走验证器、不触发
before_insert等事件、不自动处理时间戳或默认值 - 字段必须 1:1 匹配数据库列,
create_time这类得自己塞进数组里 - 返回插入行数(int),失败抛异常,不返回自增 ID 列表
用 model::saveAll($data):走模型流程,支持验证、自动时间戳、JSON 序列化、类型转换等。
立即学习“PHP免费学习笔记(深入)”;
- 适合业务强校验场景(如用户注册批量导入需检查邮箱格式)
- 性能略低,本质是循环调用每条
save(),但可关验证:saveAll($data, ['validate' => false]) - 主键自增时返回 ID 数组,否则返回布尔值
大批量(500+)必须分片 + 关闭调试 + 预处理
一次插太多,PHP 内存涨、MySQL 可能报 max_allowed_packet 或 server has gone away:
- 用
array_chunk($data, 500)拆批,每批单独调用insertAll() - 开启事务包裹每批操作:
Db::transaction(function () use ($chunk) { Db::name('user')->insertAll($chunk); }); - 关闭调试模式(
app_debug = false)减少日志开销 - 富文本或含 HTML 实体的数据,先过一遍:
$data = array_map(function ($item) { return array_map('htmlspecialchars_decode', $item); }, $data); - 避免字段不匹配报错?加
strict(false):Db::name('user')->strict(false)->insertAll($data)(只丢非法字段,不报错)
主键/唯一冲突怎么处理?框架不自动跳过
insertAll() 和 saveAll() 遇到重复主键或唯一索引,默认整个批次失败,不会“跳过”或“更新”:
- 想“存在则忽略”:用
replace()(MySQL 专用,底层是 REPLACE INTO,会删再插,慎用):Db::name('user')->replace()->insertAll($data) - 想“存在则更新部分字段”:只能手写原生 SQL,例如:
Db::execute("INSERT INTO user (id,name,email) VALUES ? ON DUPLICATE KEY UPDATE name=VALUES(name), email=VALUES(email)", $values) - 应用层不做重复判断——靠数据库唯一索引 + 捕获异常更可靠



















