2026-01-11 11:54:25
在ThinkPHP中正确使用数据库事务需遵循开启、提交/回滚的核心流程,结合闭包简化操作并规避常见陷阱,确保数据一致性。 以下是具体实现方法与注意事项:
一、事务基础操作方法ThinkPHP提供三种事务控制方式,核心逻辑均为通过startTrans()开启事务,通过commit()或rollback()结束事务:
手动控制模式
$db = Db::name('user');try { $db->startTrans(); // 开启事务 // 操作1:扣减库存 $db->where('id', 1)->setDec('stock', 1); // 操作2:创建订单(需确保使用同一连接) Db::name('order')->insert(['user_id' => 1, 'goods_id' => 10]); $db->commit(); // 提交事务} catch (Exception $e) { $db->rollback(); // 回滚事务 throw $e; // 可记录日志或进一步处理异常}关键点:所有操作需共享同一数据库连接实例,否则事务失效。
闭包自动模式(推荐)
Db::transaction(function () { Db::name('account')->where('id', 1)->setDec('money', 100); Db::name('log')->insert(['action' => 'pay', 'amount' => 100]);});优势:闭包内抛出异常时自动回滚,成功则自动提交,无需手动调用commit()/rollback()。
适用场景:适合多个操作逻辑紧密关联的场景(如订单支付、库存扣减)。
数据库引擎限制
仅支持InnoDB引擎,MyISAM等非事务引擎会导致事务失效。
检查方法:执行SHOW TABLE STATUS LIKE '表名',确认Engine字段为InnoDB。
连接实例一致性
同一事务中所有操作必须使用同一数据库连接,避免跨模型或多次调用Db::name()导致连接分离。
错误示例:$db1 = Db::name('user');$db2 = Db::name('order'); // 不同实例,事务失效$db1->startTrans();$db1->setDec('stock');$db2->insert(['order_data']); // 实际未纳入事务
避免长时间操作
事务内禁止包含远程请求、文件读写等耗时操作,防止锁表时间过长引发并发问题。
异常捕获与日志
手动模式需用try-catch包裹事务代码,确保异常时触发rollback()。
调试建议:在事务前后记录日志,定位未生效原因:try { Log::record('事务开始'); Db::startTrans(); // 操作... Db::commit(); Log::record('事务提交成功');} catch (Exception $e) { Log::error('事务回滚: ' . $e->getMessage()); Db::rollback();}
事务未生效的常见原因
引擎不支持:表使用MyISAM引擎,需修改为InnoDB。
未捕获异常:代码中缺少try-catch,导致rollback()未执行。
连接实例分离:多次调用Db::name()生成不同实例,事务状态丢失。
SQL语法错误:语句执行失败但程序未中断,导致后续操作继续执行。
闭包模式失效场景
闭包内操作涉及跨数据库连接或非InnoDB表时,自动回滚机制失效。
解决方案:确保所有操作在同一数据库的InnoDB表上执行。
通过规范使用事务方法、规避常见陷阱,可确保ThinkPHP中数据操作的原子性,适用于订单支付、库存扣减等高一致性要求的业务场景。