codeigniter 4 不内置读写分离,需手动实现或借助第三方扩展;默认只连接 default 数据库组,显式调用 database::connect('read') 或 database::connect('write') 才能切换连接,且事务必须在同一个写库实例中执行。

CodeIgniter 4 自身不内置读写分离机制,必须手动实现或借助第三方扩展。官方 Database 类默认只支持单连接,直接调用 $this->db 始终走主库 —— 这是绝大多数人踩坑的第一步。
为什么 $db = \Config\Database::connect() 永远连主库
CI4 的 Database::connect() 默认读取 app/Config/Database.php 中的 default 组配置,无论你定义了多少个数据库组(如 read、write),不显式指定组名就永远只连 default。
常见错误是:在配置里加了 'read' => [...] 和 'write' => [...],却还在控制器里写 $this->db->table('users')->get() —— 它根本不会自动路由到 read 组。
手动切换连接组:最简可行方案
在需要读操作的地方,显式调用对应连接组:
- 读操作:用
\Config\Database::connect('read')获取只读连接实例 - 写操作:用
\Config\Database::connect('write')或保持默认(若default指向主库) - 注意:每个连接实例是独立对象,
QueryBuilder方法(如select()、where())必须链在对应实例上,不能混用
示例:
// 读
$readDB = \Config\Database::connect('read');
$users = $readDB->table('users')->where('status', 1)->get()->getResult();
// 写
$writeDB = \Config\Database::connect('write');
$writeDB->table('logs')->insert(['action' => 'login', 'time' => time()]);
封装读写分离逻辑:避免重复写 connect()
硬编码连接组名容易出错,也难维护。推荐在模型基类或服务类中封装判断逻辑:
- 根据当前 SQL 类型(
SELECT/INSERT/UPDATE/DELETE)自动选连接 - 不要依赖 SQL 字符串解析(易被注释/换行干扰),而应在调用层明确标记意图
- 例如:定义
ReadModel和WriteModel两个基类,分别绑定read和write连接 - 事务必须走写库,且不能跨连接 ——
$writeDB->transStart()后所有操作都得在同一个$writeDB实例上执行
CLup + CSTLB 不是 CI4 的一部分,但能简化后端架构
如果你用的是 PostgreSQL 或 PolarDB,CLup 配合 CSTLB 可以在数据库网关层做透明读写分离,这时 CI4 应用层可仍用单连接配置,由中间件路由请求。但要注意:
-
CSTLB的CSTLB_TOKEN必须和 CLup 后台配置完全一致,否则连接被拒绝 - 弱一致性读(如 OceanBase 的
WEAK模式)需应用层主动加 hint 或 session 变量,CI4 不自动处理 - 监控
cstlb的8082端口是否存活,netstat -tlnp | grep :8082是最直接的验证方式
真正麻烦的不是配 CI4,而是确认从应用发出的每条 SQL 最终落在哪个物理节点 —— 日志里查不到,就得靠数据库侧的 pg_stat_activity 或 show processlist 反向追踪。











