ci4读写分离需手动实现,框架不内置支持;必须在.env中定义db_read和db_write独立连接组,模型中用\config\database::connect()分别加载,按sql类型显式调用对应连接。

CI4 读写分离必须手动实现,框架不内置支持
CodeIgniter 4 官方没有 read/write 连接组自动路由机制,所谓“读写分离”只能靠你自己在业务逻辑中显式选择连接对象。试图在 .env 里写 database.read.* 和 database.write.* 是无效的——CI4 只认 database.default.*,其余键名会被忽略。
真正可行的路径只有一条:定义多个独立连接组(如 db_read 和 db_write),在模型或服务类中按需加载并调用。这意味着你得主动判断哪条 SQL 是 SELECT、哪条是 INSERT/UPDATE/DELETE。
-
database.db_read.hostname和database.db_write.hostname必须分别指向从库和主库 IP(例如192.168.1.10和192.168.1.20),不能都写localhost; - 每个连接组都要完整声明
DBDriver、username、password、database,CI4 不会继承默认配置; - 务必确认从库 MySQL 已开启
read_only=ON,否则写操作发到从库会直接报错ERROR 1290 (HY000); - 不要指望
$this->db自动切换——CI4 的$this->db始终绑定default组,你要用\Config\Database::connect('db_read')才能拿到读库实例。
如何在模型中安全加载两个数据库连接
直接在 Model 构造函数里用 \Config\Database::connect() 加载多个连接,是最清晰可控的方式。避免使用 $this->load->database()(那是 CI3 的 API,CI4 已移除)。
示例代码片段(放在 app/Models/UserModel.php 中):
public function __construct()
{
$this->writeDB = \Config\Database::connect('db_write');
$this->readDB = \Config\Database::connect('db_read');
}
public function findAll()
{
return $this->readDB->table('users')->get()->getResult();
}
public function insertUser($data)
{
return $this->writeDB->table('users')->insert($data);
}
注意点:
- 连接对象命名要明确(如
$this->readDB),别用$db1/$db2这类模糊变量名; - 每次调用
\Config\Database::connect('xxx')都会复用已建立的连接(CI4 内部有连接池),无需担心频繁创建开销; - 如果某次查询失败,错误来自哪个库一目了然,不会因共用
$this->db而混淆上下文; - 别在控制器里做这个事——连接应由数据访问层(Model 或 Repository)封装,控制器只管调用方法。
测试读写分离是否真走对了库
光看 PHP 层没报错不等于流量分发正确。MySQL 本身才是最终裁判。最可靠的方法是查主从库各自的 SHOW PROCESSLIST,或者启用慢日志观察实际执行节点。
快速验证步骤:
- 在主库(
db_write)执行SET SESSION long_query_time = 0;,然后开启慢日志; - 在从库(
db_read)同样操作; - 调用
UserModel::findAll(),检查从库慢日志是否出现对应 SELECT; - 调用
UserModel::insertUser(),检查主库慢日志是否出现 INSERT; - 若 SELECT 出现在主库日志里,说明你误用了
$this->writeDB;若 INSERT 出现在从库日志,大概率是read_only没生效或账号权限没限制。
别依赖 SELECT @@hostname 这类语句——它返回的是 MySQL 实例名,不是 IP,容易误导;也别只测连通性,得测真实 SQL 路由。
容易被忽略的权限与网络问题
即使配置全对,读写分离仍可能失败,根源常在 MySQL 权限或 Docker 网络隔离上。
常见陷阱:
- 从库账号没授予
SELECT权限,只给了USAGE——执行SHOW GRANTS FOR 'reader'@'%';确认; - 主库账号被限制只能连
localhost,而 CI4 应用跑在容器里,实际来源 IP 是 Docker 网桥地址(如172.18.0.3); - 防火墙或安全组拦截了从库的 3306 端口(尤其云服务器),但主库端口开放——导致
db_read连接超时; - CI4 项目根目录的
.env文件编码含 BOM,导致database.db_read.*参数根本没被解析,框架静默回退到default组。
调试时先用 mysql -h 192.168.1.10 -u reader -p 手动连从库,再用 mysql -h 192.168.1.20 -u writer -p 连主库,排除 MySQL 层问题。CI4 的角色只是发请求,底层通不了,上面怎么写都没用。











