ci4数据库读写分离需手动实现路由逻辑,其database.php中read/write配置仅为静态连接定义,框架不自动识别sql类型或路由请求;所有操作默认走write主库,读请求必须通过显式调用如getconnection('slave1')等自定义逻辑分发,且需自行处理主从延迟、从库健康检查与负载均衡。

CI4 的 database.php 配置只控制连接,不自动路由 SQL
CodeIgniter 4 的数据库配置支持 read 和 write 数组,但这是静态连接定义,不是运行时 SQL 路由开关。框架不会自动扫描 SELECT 或 INSERT 来决定走哪个库——它只在你显式调用 $this->db->query() 或使用 Query Builder 时,根据当前激活的 connection 实例来执行。
也就是说:read 配置项只是“一堆备用连接”,不写代码逻辑,它们根本不会被用到。
- 默认情况下所有操作都走
write连接(即主库),除非你手动切换 -
$this->db->read()是无效方法;CI4 没有内置的read()切换 API - 想让读请求走从库,必须自己写逻辑:比如封装一个
getReadConnection()工厂函数,或重写BaseModel - 事务内所有查询强制走主库(这是正确行为),哪怕你手动指定了从库连接也会被忽略
从库连接必须独立配置 hostname、port 和 pool
别把多个从库地址塞进同一个 hostname 字符串里(比如 "192.168.1.10,192.168.1.11"),CI4 的 PDO 驱动不解析这种逗号分隔格式,会直接报 SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed。
每个从库需单独定义一组连接参数,并配合负载均衡策略使用:
- 用数组键名区分实例,例如
'slave1'、'slave2',并在应用层做轮询/权重选择 - 务必为每个从库连接设置
'DBDebug' => false和合理的'reconnect' => true,避免单点故障导致整个读链路中断 -
'pool' => ['max_connections' => 10]必须为每个从库单独配,否则高并发下所有读请求挤在第一个从库的连接池里 - 从库连接的
'port'不能省略,默认 3306 可能和主库冲突;线上环境建议用不同端口或明确 IP+端口组合
SHOW SLAVE STATUS\G 中 Seconds_Behind_Master 不是 0 就别切读流量
CI4 不会帮你检测主从延迟。如果你在 Seconds_Behind_Master 是 5 秒甚至持续增长时就把读请求导过去,用户大概率看到刚提交的订单查不到、新评论不显示——这不是 CI4 的 bug,是架构前提没满足。
上线前必须人工验证复制状态稳定:
-
Slave_IO_Running: Yes且Slave_SQL_Running: Yes是基本门槛,但还不够 -
Seconds_Behind_Master应长期维持在0或个位数(1~3),连续观察 10 分钟无跳变才算可信 - 如果值忽高忽低,检查主库
binlog_format = ROW是否启用、从库relay_log_recovery = ON是否开启、max_allowed_packet主从是否一致 - 别依赖
SELECT ... FOR UPDATE自动走主库——CI4 不识别这个语义,它仍会按你当前连接执行,必须手动用主库连接执行
自定义读写路由别靠正则匹配 SQL
网上常见方案用 preg_match('/^\s*(SELECT|WITH)/i', $sql) 判断读操作,但在 CI4 中极易失效:
- Query Builder 生成的 SQL 可能带换行或缩进,
^锚点失灵 - 预处理语句(
?占位符)会让原始 SQL 和执行时的 SQL 不一致,正则匹配对象错位 -
SELECT COUNT(*) FROM (SELECT ...)、SELECT * FROM table WHERE id IN (SELECT ...)等嵌套结构会被误判 - 真正安全的做法是:业务层显式声明意图,比如
$model->fromSlave()->find(),或在 service 层统一用$this->db->getConnection('slave1')
主从延迟容忍度、从库健康状态、SQL 类型边界——这些没法靠一段正则兜底。越想“全自动”,越容易在线上突然返回空数据。











