codeigniter 4读写分离不支持事务跨连接嵌套,transstart()仅作用于主库连接;从库查询不受事务隔离影响,行锁必须显式切主库并使用for update。

CodeIgniter 4 的读写分离本身不支持事务跨连接嵌套,更不自动处理锁机制 —— 这是底层数据库驱动和框架设计决定的,不是配置能绕过的限制。
读写分离下 transStart() 只作用于主库连接
CI4 的读写分离靠 $db['default']['read'][] 和 'write' 分离连接池,但事务控制(如 transStart()、transComplete())只绑定当前活动连接。一旦你调用 $this->db->table()->get(),它默认走从库;而 insert() 或 update() 强制走主库。但事务不会把从库查询“拉进”事务上下文。
- 事务开始后执行的
select若走从库,不受事务隔离级别影响,可能读到旧数据 -
transStart()内部只对主库连接调用BEGIN,从库连接完全独立 - 试图在事务中混合读从库、写主库,会破坏 ACID 的一致性语义
嵌套事务在 CI4 中实际不存在
MySQL 本身不支持真正意义上的嵌套事务(SAVEPOINT 是模拟,不是隔离的事务栈),CI4 的 transStart() 也无嵌套计数逻辑。连续调用两次 transStart() 不会创建子事务,而是覆盖或报错(取决于驱动状态)。
- 第二次
transStart()通常被忽略,或触发mysqli的 “Transaction already started” 警告 - CI4 不维护事务深度栈,
transComplete()总是对最外层事务做COMMIT或ROLLBACK - 业务逻辑里手动模拟“嵌套”(比如 try/catch 中再开事务)极易导致连接状态混乱、锁未释放
行锁必须显式走主库 + FOR UPDATE
想在读写分离场景下加行锁,不能依赖 ORM 自动行为,必须明确指定主库连接,并手写带 FOR UPDATE 的查询。CI4 的 get() 默认不加锁,也不自动切主库。
- 错误写法:
$this->db->table('users')->where('id', 1)->get()→ 走从库,无锁,且不阻塞其他事务 - 正确写法:
$this->db->query("SELECT * FROM users WHERE id = ? FOR UPDATE", [1])→ 强制走主库连接,触发行锁 - 注意:
FOR UPDATE必须在事务内执行,否则 MySQL 会隐式开启 autocommit 事务,锁释放过快 - 若使用 Query Builder,需先
$this->db->useWriteConnection()切换连接,再构建语句
高并发写冲突时,别依赖 PHP 层重试逻辑
在读写分离 + 事务 + 行锁组合下,常见误区是“先查再更新”,然后靠 PHP 捕获重复键错误重试。这在主从延迟或锁等待超时时极不可靠。
- 主从延迟导致
SELECT读不到刚插入的数据,重试逻辑误判为“可写” -
FOR UPDATE等待超时(默认 50 秒)会抛出HY000/1205死锁错误,不是唯一约束冲突 - 真正健壮的做法:所有关键写路径统一用
INSERT ... ON DUPLICATE KEY UPDATE或REPLACE INTO,避开“查-改”竞态 - 若必须用锁,确保整个操作(查+改)在同一个主库连接、同一事务内完成,且避免跨方法拆分
真正的难点不在语法,而在连接归属、事务边界和锁生命周期的对齐 —— 这三者一旦错位,日志里看不出错,但数据就是会不一致。











