ci4的database类不原生支持读写分离,需手动封装路由层:定义'default'(主库)和'slave'(从库)配置组,通过重写basemodel的读写方法显式指定连接,并注意事务、复制延迟及监控策略。

CI4 的 Database 类不原生支持读写分离
CodeIgniter 4 的 Database 类默认只维护单个连接,$db = \Config\Database::connect() 返回的是一个固定连接实例,没有内置的主从路由逻辑。你不能靠改配置文件就自动实现“写走主库、读走从库”。强行在 app/Config/Database.php 里填多个 DNS 并不会触发切换——它只会让你的 connect() 调用失败或随机连上某一个。
必须自己封装路由层。常见做法是:
- 定义两个独立配置组:
'default'(主库)和'slave'(从库),都注册进Database.php - 用
Service或BaseModel子类控制连接选择:写操作调用\Config\Database::connect('default'),读操作调用\Config\Database::connect('slave') - 避免在控制器里硬编码连接,否则后期难以统一管控
DBGroup 不等于读写分离,别被名字误导
CI4 的 DBGroup 是为分库(sharding)设计的,比如按用户 ID 拆到 db_01/db_02,它不处理主从角色区分。即使你把主库和从库都塞进同一个 group,$builder->from('users')->get() 仍只会连第一个配置项,不会按 SQL 类型自动路由。
真正要实现读写分离,得绕过 Builder 的默认行为:
- 重写
BaseModel的find()、findAll()等读方法,内部显式调用从库连接 - 写方法如
insert()、update()必须强制使用主库连接 - 事务内所有操作(包括 SELECT)必须走主库,否则会报错或数据不一致
监控连接数:别只看 show status like 'Threads_connected'
MySQL 的 Threads_connected 是全局连接总数,对读写分离场景意义有限。你需要分别监控主库和每个从库的活跃连接,否则看不出从库是否真在分担压力。
一款AI工具,主要用于在主代理响应前,并行运行Kimi K2.5和GPT 5.3 Codex,注入双方观点以增强认知多样性,适合需要提升相关任务效率的用户。
推荐做法:
- 主库执行:
SHOW PROCESSLIST,过滤User和Command,重点关注Query状态的连接来源 IP - 从库执行相同命令,对比主库的
SELECT数量是否明显下降 - 在 CI4 应用层加日志:每次
connect()时记录连接名('default'或'slave')和时间戳,聚合后分析分布比例 - 注意连接池干扰:如果用了
persistent连接,Threads_connected可能长期偏高,不代表实时并发
容易被忽略的复制延迟导致的脏读
CI4 不管数据库底层同步状态。当你从从库查刚插入的数据,大概率查不到——这不是框架问题,而是 MySQL 主从异步复制的固有特性。业务代码里不能假设“写完立刻可读”。
应对方式很实际:
- 对强一致性要求高的场景(如用户注册后跳转个人页),强制用主库连接查一次:
\Config\Database::connect('default')->table('users')->where('id', $id)->get() - 报表、统计类查询一律走从库,接受几秒到几分钟的数据延迟
- 不要依赖
sleep(1)等待同步——网络抖动或大事务会让延迟不可预测
真正的难点不在配置,而在判断哪条 SELECT 该走主、哪条该走从。这个决策点藏在业务语义里,不是靠技术开关能自动解决的。










