ci4 不支持原生读写分离,其 database.default 配置仅允许单组连接参数;需手动定义 read/write 连接组并在 .env 中配置,再通过自封装工厂函数调用 configdatabase::connect('group') 显式获取连接。

CI4 的 database.default 配置不支持读写分离
CodeIgniter 4 官方数据库配置(database.default.*)只允许定义一套连接参数,没有内置的 read / write 分组字段。试图在 .env 里加 database.read.hostname 或修改 app/Config/Database.php 中的 $default 数组,都不会被框架识别——CI4 的 ConfigDatabase::connect() 只读取 database.default.* 这一组。
常见错误现象:配置了多套 host,但所有查询(包括 INSERT、UPDATE)全打到同一个库上;或手动改 Database.php 后报错 Call to undefined method CodeIgniter\Database\MySQLi\Connection::read()。
- CI4 不像 Laravel 那样有
connections.read+connections.write的原生支持 -
app/Config/Database.php是只读模板,框架启动时完全忽略它;真正生效的只有.env - 若强行在
Database.php中 return 多个连接实例,后续调用$this->db仍只会拿到 default 实例
手动实现读写分离需绕过默认连接机制
要让 CI4 实际走不同库,必须跳过 $this->db 自动注入,改用 ConfigDatabase::connect('group_name') 显式获取连接,并配合查询类型做路由判断。
例如,在控制器中:
// 写操作走主库
$writeDB = ConfigDatabase::connect('write');
$writeDB->table('users')->insert(['name' => 'alice']);
<p>// 读操作走从库
$readDB = ConfigDatabase::connect('read');
$users = $readDB->table('users')->get()->getResult();
</p>
关键点在于:你得自己定义 'write' 和 'read' 这两个连接组,并让它们在 .env 中可被识别。
- 先在
.env中添加两组配置:database.write.hostname、database.write.username等,以及database.read.hostname等 - 然后在
app/Config/Database.php中重写init()方法(或覆盖getConnection()),根据传入的 group 名动态加载对应 .env 配置 - 注意:CI4 的
Database类没有connect('xxx')的原生支持,必须自己封装一个工厂函数,否则会报InvalidArgumentException: Group "read" does not exist
Session 粘滞不是 CI4 自带功能,必须靠应用层控制
所谓“Session 粘滞”,是指同一用户的所有请求(尤其含写操作)始终落在同一台从库上,避免主从延迟导致读不到刚写入的数据。CI4 的 Session 类(CodeIgniterSessionSession)本身不感知数据库拓扑,也不提供 sticky_to_slave 开关。
真实场景下,粘滞逻辑只能由你自己编码实现:
- 在登录成功后,把当前用户分配的从库编号存入 Session:
session()->set('slave_id', 1) - 后续读请求,先读
session()->get('slave_id'),再调用对应编号的ConfigDatabase::connect("read_{$id}") - 写请求一律走
write组,且写完后可主动触发一次短时缓存失效(如cache()->delete("user_{$uid}")),降低对粘滞的依赖 - 不要依赖 IP 做粘滞——负载均衡器可能轮换源 IP,且移动端 IP 易变
最容易被忽略的兼容性陷阱
CI4 的 Query Builder 在底层复用同一个 Connection 实例,一旦你手动切换连接,$builder->from()、$builder->where() 等链式调用不会自动绑定到新连接。这意味着:
- 不能混用:
$this->db->table('x')->get()和ConfigDatabase::connect('read')->table('x')->get()共存于同一请求中,容易因事务上下文错乱导致数据不一致 - Query Builder 的
join()跨库时无效——MySQL 不允许SELECT * FROM master.users JOIN slave.logs这种语法,除非用 FEDERATED 引擎(不推荐) - Migration 和 Seeder 默认只跑在
default连接上,部署时若没切到write组,可能建表失败却无提示
真正在生产环境跑读写分离,得接受一个事实:CI4 不是为这个场景设计的。每加一层手动路由,就多一分维护成本和出错概率。不如先确认主从延迟是否真到了影响业务的程度,再决定要不要上。











