ThinkPHP 5.0中实现用户注册双表写入需依赖InnoDB事务,必须删除config/database.php中'engine'=>'myisam'配置并确认db:info显示InnoDB;推荐用Db::transaction闭包自动管理,或手动startTrans/commit/rollback;严禁ProfileModel自定义连接导致事务断裂;5.1.13+支持savepoint实现子事务回滚。

在ThinkPHP 5.0中实现用户注册时同步写入users表和profiles表,必须确保两表操作要么全部成功、要么全部撤销,否则数据库会残留孤立记录——这正是事务要解决的核心问题。
确认事务生效前提
打开config/database.php,检查'type' => 'mysql'配置下是否显式设置了'engine' => 'myisam';如果有,【必须删除这一行】,否则InnoDB引擎不会被启用。
执行php think db:info命令,终端输出中必须出现InnoDB字样;若显示MyISAM,说明事务已静默失效,后续所有startTrans调用都形同虚设。
这一步不能跳过——哪怕代码写得完全正确,引擎不对,事务就根本不存在。
立即学习“PHP免费学习笔记(深入)”;
推荐新手用Db::transaction闭包方式
方法一:自动提交/回滚,无需手动写try-catch
use think\facade\Db;
Db::transaction(function () {
UserModel::create(['name' => '李四', 'email' => 'li@example.com']);
ProfileModel::create(['user_id' => Db::getLastInsID(), 'bio' => '前端工程师']);
});
闭包内任意位置抛出异常(包括SQL语法错误、字段缺失、唯一键冲突),系统会自动触发rollback,users表和profiles表都不会留下任何数据。
方法二:手动控制,适合需要中间校验或日志记录的场景
第一步:Db::startTrans();
第二步:把所有数据库操作放进try块,注意UserModel::create()返回的是模型实例,不是ID;要用Db::getLastInsID()获取刚插入的主键值,否则ProfileModel::create()里传的user_id会是0或null。
第三步:成功后调用Db::commit();,失败时在catch里执行Db::rollback();并重新抛出异常,否则上层控制器无法感知错误。
避免跨模型连接导致事务断裂
如果ProfileModel内部定义了独立的数据库连接配置(比如指定了不同host或database),【Db::transaction闭包内的ProfileModel::create()将脱离当前事务上下文】,此时users表已提交,profiles表却因异常回滚——数据彻底不一致。
验证方式:在ProfileModel::create()前加throw new \Exception('强制中断');,执行后检查users表是否为空;如果不为空,立即排查ProfileModel类中是否含有protected $connection或protected $db_config等自定义连接声明。
统一连接的最简做法是:两个模型都不声明连接配置,全部依赖全局database.php设置。
事务嵌套与保存点使用
ThinkPHP 5.1.13+支持savepoint机制,可在大事务中设子断点:
Db::startTrans();
try {
Db::table('order')->insert(['no' => 'ORD20260806001']);
Db::execute('SAVEPOINT sp1');
try {
Db::table('order_item')->insertAll($items);
} catch (\Exception $e) {
Db::execute('ROLLBACK TO SAVEPOINT sp1');
// 继续执行替代逻辑,如写入日志表
Db::table('log')->insert(['msg' => '订单明细插入失败']);
}
Db::commit();
} catch (\Exception $e) {
Db::rollback();
throw $e;
}



















