商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > ThinkPHPMySQL事务怎么用_ThinkPHP数据库事务操作【方法】

ThinkPHPMySQL事务怎么用_ThinkPHP数据库事务操作【方法】

  发布于2026-07-08 阅读(0)

扫一扫,手机访问

说个现实情况,很多人在项目里写了事务代码,结果数据该写还是写进去了——这不是框架有问题,而是几个细节没到位。ThinkPHP 的 MySQL 事务能不能用?能用。但默认状态下它可能根本没生效。核心就卡在三处:数据库引擎是不是 InnoDB、自动提交有没有关、异常有没有被正确捕获并触发回滚。一个没注意,事务就是个摆设。

先说最省心的方案:Db::transaction() 闭包事务

这种方式把 startTrans()commit()rollback() 全封装好了。闭包一跑,只要内部抛异常,事务自动回滚;跑完了没出问题,自动提交。代码写起来干净,不容易漏掉某个清理步骤。

不过有几个细节不能踩:

  • 闭包内只要 throw new \Exception 或者出现未捕获错误,事务就会回滚,不需要自己写 try-catch
  • 闭包执行完没异常,事务自动提交,省心但也没给手动判断留太多余地
  • 它不支持跨连接事务。如果同时操作两个不同的数据库,要么报错,要么静默失败,不会自动回滚另一个库的操作

举个例子就清楚了:

Db::transaction(function () {
    Db::table('order')->insert(['uid' => 100, 'status' => 'created']);
    Db::table('stock')->where('id', 1)->dec('num', 1);
    // 这里有个常见的坑:dec 返回 false 不会让闭包中断
    // 必须自己检查库存,如果变成负数,手动抛异常
    if (Db::table('stock')->where('id', 1)->value('num') < 0) {
        throw new \Exception('库存不足');
    }
});

看到没有?判断必须自己做,false 不会自动触发回滚——你期待的“如果减失败了就回滚”,框架并不会替你判断。

手动事务 Db::startTrans():坑多,但给了你全部控制权

有些人觉得闭包不够灵活,喜欢手动操作。从 startTrans()commit() 再到 rollback(),全自己管。但这套手动流程,往往就断在哪几个环节?

  • Db::startTrans() 必须在所有操作之前调用,而且只能调一次。重复调用不报错,但也不会生效
  • 每个 DB 操作之后,必须检查返回值或捕获异常。注意,delete()update() 成功返回影响行数,失败是 false 或抛异常,不能只靠一个 if 判断完事
  • 错误判断不能依赖 if ($a && $b) 这样的链式检查,因为部分 SQL 语法错误(比如字段不存在)会直接抛异常,不会被 if 捕获
  • catch 块里必须显式调用 Db::rollback()。PHP 异常没被捕获时,事务不会自动回滚,它会留在那里等超时
  • 事务结束后,状态不会自动重置。如果后续请求复用同一个连接(特别是 FPM 模式下),可能莫名其妙卷入残留事务

代码看着没问题,数据还是改了——常见底层原因

很多回滚失败的问题,根本不在代码层面,而是数据库层或连接配置的锅。来,直接列出来对照检查:

  • 表引擎是 MyISAM:执行 SHOW CREATE TABLE user,确认输出里是 ENGINE=InnoDB。老项目迁移时,字典表、日志表这类容易被漏改
  • MySQL 连接开启了自动提交:用 Db::connect()->getPdo()->getAttribute(PDO::ATTR_AUTOCOMMIT) 查一下,应该是 0 而不是 1
  • 混用了 InnoDB 和 MyISAM 表:哪怕只有一条 INSERT 进了 MyISAM 表,这条语句立刻生效,之后你哪怕调 rollback(),它也纹丝不动
  • 事务里执行了 DDL 语句(比如 ALTER TABLE)、或者调用了存储过程,这些操作会触发隐式提交,事务会被提前切断
  • 事务太长,被 MySQL 超时杀掉。innodb_lock_wait_timeout 默认 50 秒,长事务很容易触到,但 PHP 这边 catch 不到,你以为还在事务里,其实已经断了

高并发下,写事务不难,难的是设计

事务本身不解决并发冲突,它只是给了你一块“临时保护区”。如果并发场景没设计好,事务反而会把问题放大。几个要点:

  • 事务里别做外部调用——curl 请求、Redis 写入、日志记录,这些事情会拉长事务时间,增加锁竞争
  • 更新顺序要一致。多个事务同时操作 userorder 表,必须固定先更新 user 再更新 order,否则很容易死锁
  • 尽量用 WHERE 精确匹配主键或唯一索引,避免全表扫描加锁。比如 UPDATE ... WHERE status = 'pending' 可能锁住大量行
  • ThinkPHP 的嵌套事务靠 sa vepoint 实现,但 sa vepoint 不是真正嵌套。外层 rollback 会连带清除所有 sa vepoint,别指望它能做复杂回滚
  • 余额类场景,加乐观锁比死等行锁更靠谱。用 UPDATE account SET balance = balance - 100 WHERE id = 1 AND version = 5,失败了就重试

ThinkPHPMySQL事务怎么用_ThinkPHP数据库事务操作

说到底,真正难的不是写 Db::transaction(),而是确认每张表都是 InnoDB、每次操作都落在同一个连接、每个失败分支都走到了 rollback()。这些细节漏掉一个,事务就是纸老虎。

本文转载于:https://www.php.cn/faq/2421696.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注