CodeIgniter事务提交失效主因是事务未真正启动或被隐式提交:InnoDB引擎、autocommit=0是前提;DDL语句、混合原生查询、异常未捕获或连接复用均会导致trans_commit()静默无效。

CI自定义类中事务提交失效,大概率不是代码写错了,而是事务根本没真正启动,或者被隐式提交了。
为什么 $this->db->trans_commit() 总是不报错却没效果?
CodeIgniter 的事务控制依赖于底层连接状态,trans_commit() 和 trans_rollback() 都只对“当前活跃事务”起作用。如果事务早已结束(比如 AUTOCOMMIT=1、执行了 DDL、或引擎不支持),调用它们就等于对空气发号施令。
常见现象:
- 执行完
$this->db->trans_commit()后查数据库,数据已写入,但你期望它在异常时回滚 —— 实际上事务压根没开启 - 没有报错,也没有警告,日志里也看不出异常 —— 因为 CI 默认静默忽略无效的 commit/rollback
必须先确认事务是否真在运行:
- 检查引擎:
SHOW CREATE TABLE `your_table`,确保含ENGINE=InnoDB - 确认 autocommit:
SELECT @@autocommit,应为0;若为1,需显式调用$this->db->trans_start()(CI 会自动设SET AUTOCOMMIT=0) - 避免在事务块中执行
CREATE/ALTER/DROP—— 它们会触发隐式提交,之后所有trans_rollback()都无效
CI 3.x 中 trans_complete() 为何有时跳过 rollback?
trans_complete() 是 CI 的“自动收尾”方法:它内部判断是否有错误,有则 rollback(),否则 commit()。但它只检查 CI 自己捕获的查询错误(如 $this->db->query() 返回 false),**不感知 PHP 异常**。
典型陷阱:
- 你在事务块里抛出
throw new Exception('xxx'),但没 catch,trans_complete()根本没机会执行 - 你用了
try/catch,但在 catch 里忘了调用$this->db->trans_rollback(),而直接return或exit - 你混合使用了 CI 查询和原生
mysqli_query()—— 后者绕过 CI 事务管理,trans_complete()对它完全无感
正确做法是:不用 trans_complete(),改用手动流程 + 显式异常捕获:
$this->db->trans_start();
try {
$this->db->insert('users', ['name' => 'Alice']);
$this->db->update('logs', ['status' => 'done'], ['id' => 1]);
// 模拟业务校验失败
if (empty($some_condition)) {
throw new RuntimeException('业务规则不满足');
}
$this->db->trans_commit();
} catch (Exception $e) {
$this->db->trans_rollback();
log_message('error', '事务失败: ' . $e->getMessage());
throw $e;
}
CI 4.x 使用 Query Builder 时事务失效的隐藏条件
CI 4 的 $db->transStart() 和 $db->transComplete() 行为与 3.x 类似,但多了两个易忽略点:
-
$db实例必须是同一个 —— 如果你在模型里用$this->db,又在 service 层 new 了一个新Database实例,事务状态不共享 - 启用 query caching(
$db->cacheOn())后,缓存读取不走事务上下文,但写操作仍受事务控制,容易造成“读到旧数据”的错觉,误以为回滚没生效 - 事务内调用
$db->table()->insertBatch()失败时,CI 不自动标记事务为 error,transComplete()仍会 commit —— 必须手动检查insertBatch()返回值或捕获DatabaseException
建议统一用 try/catch + 显式 transRollback(),并禁用事务块内的缓存:
$db = \Config\Database::connect();
$db->transStart();
try {
$db->table('orders')->insert(['amount' => 100]);
$db->table('inventory')->update(['stock' => 99], ['id' => 1]);
if (!$db->table('orders')->getInsertID()) {
throw new RuntimeException('订单插入失败');
}
$db->transCommit();
} catch (\CodeIgniter\Database\Exceptions\DatabaseException $e) {
$db->transRollback();
// 处理 DB 层错误
} catch (Exception $e) {
$db->transRollback();
// 处理业务逻辑错误
}
连接复用导致“回滚后数据还在”的真实原因
这不是 CI 的 bug,而是 MySQL 连接池复用带来的副作用:前一次事务异常未 rollback,连接被放回池中;下一次请求拿到这个连接时,它的事务状态仍是 active,新查询可能读到未提交的脏数据,让你误以为“回滚失效”。
CI 本身不管理连接池,但如果你用了 PDO + persistent connection,或部署在 Apache prefork 模式下,就极易复现。
验证方式:
- 在事务前后加
SELECT @@in_transaction,看是否始终为1 - 重启 Web 服务后问题消失 → 基本可判定是连接复用残留状态
修复方案:
- 禁用持久连接:
'pconnect' => false(CI 3)或'persistent' => false(CI 4) - 每次请求结束前强制清理:
$this->db->transComplete();放在控制器末尾(仅限单请求单事务场景) - 更可靠的做法:不在连接层依赖自动清理,而是在每个事务块严格配对
transStart()/transCommit()或transRollback()
最麻烦但最稳妥的点:事务内永远不要做 HTTP 请求、文件 I/O 或 sleep —— 这些会让连接长时间空闲,增加复用概率,也违背事务“短小快”的原则。

















