应使用Db::transaction()闭包方式确保多表操作一致性,其自动管理事务生命周期、共享连接实例,并在异常时自动回滚;需显式捕获非PDO异常并重新抛出。

如果您在ThinkPHP6.x中执行涉及多个数据表的写入或更新操作,但发现部分成功、部分失败导致数据状态不一致,则可能是事务未正确启用或控制逻辑存在缺陷。以下是确保多表数据操作一致性与安全性的方法:
一、使用Db::transaction()闭包方式
该方式将事务生命周期封装在匿名函数内,自动管理开启、提交与回滚,避免手动调用遗漏,且所有数据库操作共享同一Db连接实例,防止跨模型事务上下文丢失。
1、在业务逻辑中调用Db::transaction()并传入闭包函数。
2、在闭包内依次执行对多个表的操作,例如插入订单记录、扣减库存、写入日志。
立即学习“PHP免费学习笔记(深入)”;
3、若闭包内任意操作抛出异常,事务将自动回滚;若正常结束,则自动提交。
4、必须显式捕获非PDO异常(如业务校验异常),并在catch块中重新抛出,否则Db::transaction()不会触发回滚。
二、手动控制事务(startTrans/commit/rollback)
适用于需在事务过程中插入条件判断、分段执行或嵌入非DB操作的场景,但要求开发者严格保证事务方法调用的配对性与连接一致性。
1、调用Db::startTrans()显式开启事务。
2、执行第一个模型的写入操作,例如User::create($userData)。
3、执行第二个模型的关联操作,例如Profile::create($profileData)。
4、检查各操作返回值是否有效,确认无逻辑错误后调用Db::commit()。
5、所有startTrans、commit、rollback必须由同一个Db类静态调用,禁止混用不同模型实例的方法。
三、事务嵌套与保存点控制
当主事务中需局部回滚而不影响整体流程时,可利用InnoDB的SAVEPOINT机制实现嵌套事务语义,ThinkPHP6通过Db::savepoint()和Db::rollbackTo()支持该能力。
1、在主事务内调用Db::savepoint('sp1')创建保存点。
2、执行可能失败的子操作,例如第三方接口回调更新状态。
3、若子操作失败,调用Db::rollbackTo('sp1')回退至该点,主事务其余部分仍可继续。
4、保存点名称必须唯一且显式指定,不可依赖自动生成名,否则回滚不可控。
四、强制走主库与读写分离适配
在启用了读写分离的环境中,事务期间所有SQL必须路由至主库执行,否则从库无法感知未提交变更,导致查到脏数据或丢失写入。
1、在事务开始前设置Db::setConfig(['read_master' => true])。
2、或在每个查询中显式指定主库连接:Db::master()->name('table')->insert(...)。
3、验证事务内所有Db::name()调用均未命中只读连接池。
4、禁止在事务中调用任何可能触发从库查询的操作(如未加master()修饰的select)。
五、结合数据库约束强化一致性
事务仅解决执行过程的原子性,无法替代数据结构层面的完整性保障。联合唯一索引、外键约束等数据库原生机制是防重与关联一致的最后防线。
1、为订单与商品关系表添加联合唯一索引:$table->unique(['order_id', 'sku_id'], 'uk_order_sku')。
2、在迁移文件的down()方法中显式删除该命名索引:$table->dropUnique('uk_order_sku')。
3、确保字段顺序与业务查询where条件完全一致,避免索引失效。
4、所有涉及NULL值的字段必须定义为NOT NULL DEFAULT '',否则联合唯一索引对NULL不生效。



















