db::transaction()是tp6+唯一推荐的事务入口,必须用它包裹批量操作;insertall()需分片(如每批≤400条)避免卡死;禁止混用saveall()与insertall(),且事务内不可调用模型方法。

Db::transaction() 是唯一推荐的事务入口
TP6+ 必须用 Db::transaction() 包裹批量操作,别再用 startTrans()/commit() 手动控制。前者是函数式封装,异常时自动回滚;后者漏 catch 就等于没事务。
常见错误现象:Db::startTrans() 后调用 insertAll() 报错但没 catch,脚本继续执行后续 SQL,事务实际已失效。
-
Db::transaction()内部已处理 PDO 异常捕获和连接复用,无需手动try/catch(除非你要自定义错误日志) - 传入的闭包函数必须返回结果,否则事务可能不提交(例如函数末尾没
return,PHP 返回null,Db 类会认为“无返回值 = 未完成”) - 不要在事务闭包里调用模型方法(如
UserModel::saveAll()),模型可能使用独立数据库连接,导致事务不生效
insertAll() 必须配合分片,否则事务容易卡死
单次 insertAll() 插入 5000 行?MySQL 可能因 max_allowed_packet 拒绝或锁表超时,事务迟迟不提交,阻塞其他写请求。
实操建议:
- 用
array_chunk($data, 400)切片,每批 ≤ 400 条(TP6 默认max_allowed_packet=4M下较安全) - 每批调用一次
Db::name('table')->insertAll($chunk),不要把所有数据塞进一个insertAll() - 切片后别在循环里反复 new Query 对象,复用同一个
Db::name('table')实例 - CLI 脚本记得加
gc_collect_cycles(),防止内存随批次累积
事务内不能混用 saveAll() 和 insertAll()
看起来都是批量插入,但底层机制不同:saveAll() 走模型层,触发验证、事件、自动时间戳;insertAll() 直接拼 SQL,绕过模型。两者若共享同一事务,可能因连接实例不一致导致部分操作不在事务作用域内。
使用场景判断:
- 需要字段过滤、验证、自动填充(如
create_time)→ 用saveAll(),且确保整个事务只用模型 - 导入清洗后的结构化数据(如 Excel 解析结果)、追求极致性能 → 用
insertAll(),自己补全时间字段 - 二者混用 = 事务失效高发区,尤其在 TP5.1+ 和 TP6 混合部署环境
insertAll 失败不会自动中断事务,必须靠异常驱动
insertAll() 返回的是影响行数(int),不是布尔值。主键冲突时抛 think\db\exception\DataNotFoundException,但如果你没开启严格错误模式,它可能只返回 0 而不中断脚本。
关键点:
- 事务是否回滚,取决于有没有未捕获的异常,而不是返回值是否为 0
- 唯一稳妥方式:确保
Db::transaction()闭包内所有操作都可能抛异常(如校验空数据、检查必填字段),不要用if ($result > 0)做逻辑分支 - 如果业务要求“跳过重复数据”,
insertAll()不支持INSERT IGNORE,得换Db::execute('INSERT IGNORE INTO ...'),但此时已脱离事务安全边界,需自行权衡
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











