事务未回滚的根本原因是未正确配对使用 $this->db->trans_start() 和 $this->db->trans_complete(),导致事务根本未启动;ci 3 不支持嵌套事务,且 trans_status() 仅在 trans_complete() 后有效。

事务没回滚?先确认 $this->db->trans_start() 和 $this->db->trans_complete() 是否成对出现
CodeIgniter 的事务不是自动开启的,漏掉任意一个调用,整个事务流程就根本没启动——看起来像“回滚失效”,其实是压根没进事务。常见现象是:前几条 INSERT 成功了,最后一条 UPDATE 报错,但前面的数据仍留在库中。
必须保证:
-
$this->db->trans_start()在所有数据库操作之前调用,不能放在if分支里(除非你 100% 确保该分支一定执行) -
$this->db->trans_complete()紧跟在全部查询之后,且只能调用一次;重复调用不会报错,但第二次起无效 - 二者必须出自同一个 DB 实例,不能跨模型或跨
$this->load->database()新建连接
为什么 $this->db->trans_rollback() 没效果?
手动回滚只在事务已启动、尚未被 $this->db->trans_complete() 关闭时有效。CI 默认在 trans_complete() 内部自动判断:只要期间任一查询返回 false(比如语法错误、字段不存在、违反约束),就自动回滚。所以绝大多数场景下,你不需要、也不应该提前手动调 $this->db->trans_rollback()。
它真正适用的场景只有两种:
- 校验逻辑失败(比如业务规则不满足),你想主动放弃后续 SQL,并回滚已执行的部分
- 需要在
trans_complete()之前提前终止事务(例如某个条件不满足,直接退出)
注意:$this->db->trans_rollback() 不抛异常、不中断执行流,调完后必须自己 return 或 exit,否则后续代码还会继续跑。
事务里调模型方法,为什么部分操作没回滚?
因为模型内部如果用了 $this->load->database() 或未显式传入当前 DB 实例,就会新建一个连接——而 CI 的事务状态是绑定在单个 DB 实例上的,新连接完全脱离事务上下文。
典型表现:控制器开了事务,然后连续调 $this->user_model->update() 和 $this->order_model->insert(),结果只有第一个生效,第二个失败时前一个没回滚。
解决办法只有两个:
- 所有模型方法都加一个可选参数
$db = null,内部统一用传入的实例执行查询($db->query()而非$this->db->query()) - 干脆不在事务块里调模型,把 SQL 操作收拢到控制器或 service 类,全程只用一个
$this->db实例
事务始终不生效?检查数据库引擎和连接配置
CI 的事务包装器再正确,底层数据库不支持也没用。MySQL 下若表引擎是 MyISAM,BEGIN/ROLLBACK 语句会被静默忽略——查 SHOW CREATE TABLE your_table,确认显示 ENGINE=InnoDB。
另外要注意连接配置陷阱:
- PHP 8.1+ 配合 MySQLi 时,若开启了
emulate_prepare或stricton,可能绕过事务控制 - SQLite 默认支持事务,但不支持嵌套;PostgreSQL 对
SAVEPOINT更敏感,CI 原生不封装,需手写$this->db->query('SAVEPOINT sp1') - 调试时打开
$this->db->save_queries = TRUE,然后在trans_complete()后立刻 dump$this->db->queries,确认是否真发出了BEGIN和ROLLBACK
最隐蔽的问题往往出在:你以为在事务里执行的语句,其实走的是另一个连接、另一个引擎、甚至另一套配置。事务本身没有魔法,它只是把一组操作交给数据库引擎按原子性处理——前提是,你真的把它交到了正确的引擎手上。











