insertall 默认返回影响行数而非自增id列表,因其执行单条多值insert语句,last_insert_id()仅返回首条id;需依赖全部id时应改用循环insert或原生sql。

insertAll 默认不返回自增 ID,别指望它像 insert 一样给结果
ThinkPHP 的 insertAll 设计初衷是批量插入,性能优先,所以默认返回的是影响行数(int),不是自增 ID 列表。哪怕你用的是 MySQL + 自增主键,调用 $model->insertAll($data) 后拿到的只是类似 3 这样的数字,不是 [101, 102, 103]。
原因在于底层执行的是单条 INSERT INTO ... VALUES (),(),() 语句,MySQL 的 LAST_INSERT_ID() 只返回第一条记录的 ID,TP 没做额外查询去补全后续 ID —— 这是刻意为之,避免 N+1 查询或额外开销。
想拿全部自增 ID,得自己拼 SQL 或分批 insert
如果业务强依赖每条记录的 ID(比如插入后立刻关联写入中间表),有两条可行路径:
- 改用循环调用
insert:每次插入一条,用$model->getLastInsID()拿到当前 ID,适合数据量小( - 手写原生批量插入 + 手动计算 ID:先查当前最大 ID(
SELECT MAX(id) FROM table),再用insertAll插入,最后按插入顺序推算出 ID 数组(前提是表无并发写入、id 严格自增无跳号)——风险高,仅限低并发内部工具脚本 - 用 PDO 的
lastInsertId()配合INSERT ... SELECT或临时表中转:更重,但可控,适合需要事务包裹的复杂场景
MySQL 8.0+ 可用 RETURNING(但 ThinkPHP 不原生支持)
MySQL 8.0.29+ 支持 INSERT ... VALUES (...),(...) RETURNING id,能直接返回所有新 ID。但 ThinkPHP 的 insertAll 并未封装该语法,必须绕过 ORM,走原生查询:
$sql = "INSERT INTO user (name, email) VALUES (?, ?), (?, ?) RETURNING id"; $result = Db::execute($sql, ['a', 'a@b.com', 'b', 'b@b.com']); // $result 是 PDOStatement,需 fetchAll() 获取 ID 数组
注意:RETURNING 在 TP 中不会自动映射为模型实例,返回的是原始二维数组;且该语法不兼容 MySQL 5.7 或 MariaDB,上线前务必确认数据库版本。
最容易被忽略的坑:事务里 insertAll + getLastInsID 只返回第一个 ID
很多人在事务中这样写:
Db::transaction(function () {
$ids = User::insertAll([['name'=>'x'], ['name'=>'y']]);
$firstId = User::getLastInsID(); // ❌ 永远只等于第一条的 ID,不是数组
});
这是典型误解:getLastInsID() 和 insertAll 无关,它读的是 PDO 底层最后一次 INSERT 的 lastInsertId() 值,而 insertAll 是一次执行,所以只能取到第一个。如果你没意识到这点,后续用 $firstId + 1 去猜第二条 ID,线上就会出错。
真正安全的做法,要么放弃批量、改用循环 + getLastInsID(),要么彻底脱离 ORM,用原生方式控制返回值。没有银弹,得看你的数据量、并发要求和数据库能力边界。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











