CodeIgniter联表更新必须用事务包裹,调用transStart()和transComplete()成对控制,操作均需在二者之间,完成后检查transStatus()判断成败,且须共用同一$db实例。

CodeIgniter 联表更新多张数据表,必须用事务包裹,否则极易出现数据不一致。它不支持嵌套事务,也不提供自动回滚的闭包语法,所有操作要靠手动控制、显式检查状态。
事务必须成对调用 transStart() 和 transComplete()
联表更新涉及多张表写入(比如同时更新 orders、order_items、payments、logs),每一步都不能脱离事务上下文:
- 必须先调用 $this->db->transStart(),标记事务起点
- 所有 update()、insert()、delete() 操作必须写在这之后、$this->db->transComplete() 之前
- 漏掉 transComplete(),或把某条语句写在事务块外,该语句将立即提交,失去原子性
- transStart() 本身不开启数据库级事务,只是做标记;真正执行 COMMIT 或 ROLLBACK 是在 transComplete() 时才发生
成功与否只看 transStatus(),别信 affected_rows 或异常
CI4 的事务机制静默失败:即使某条 update() 返回 FALSE,也不会抛异常,而是等 transComplete() 自动回滚。因此:
- 必须在 $this->db->transComplete() 调用后,立刻检查 $this->db->transStatus()
- 它返回 TRUE 表示全部成功并已提交;返回 FALSE 表示至少一条失败、已回滚
- 不能依赖 $this->db->affected_rows() 判断——它只反映最后一条语句影响行数,无法代表整个事务结果
- 也不能 try-catch 捕获异常——CI4 事务默认不抛异常,除非你主动开启 db_debug(生产环境应关闭)
多表操作必须共用同一个 $db 实例
如果你在事务中调用了不同模型的方法,而这些模型各自 new Database() 或 $this->load->database(),就可能创建多个连接实例,导致事务失效:
- 所有联表更新操作,应统一使用控制器或主模型中的 $this->db 实例
- 子模型若需参与事务,不要独立加载数据库,而是接收外部传入的 $db 对象,或通过服务容器共享实例
- 避免在事务块内调用另一个也含 transStart() 的方法——CI4 不支持 savepoint,会干扰外层事务状态
联表更新建议拆成单表操作+外键约束保障一致性
CodeIgniter 原生不支持类似 UPDATE ... JOIN 的语法,也不推荐在一条 query() 中硬写多表 UPDATE(可读性差、难维护、易出错)。更稳妥的做法是:
- 用主表 ID 作为枢纽,分步更新:先改主表(如 orders.status = 'shipped'),再根据该 ID 更新从表(order_items.shipped_at、payments.status、logs.action = 'ship')
- 确保相关字段有外键约束和索引(如 order_id 字段建索引),提升 WHERE 查询效率
- 若业务强要求“一次发多个 UPDATE”,可用原生 SQL + query(),但必须仍包裹在 transStart()/transComplete() 中,并同样检查 transStatus()

















