CI4通过.env文件用点号语法定义read/write两套数据库连接,如database.read.hostname和database.write.hostname;Model构造函数注入连接名并调用Config\Database::connect($connection)显式指定,事务或强一致性读需强制走write连接。

CI4 里怎么定义 read/write 两套数据库连接?
CI4 不认 database.php 里多个分组的写法(比如 $db['read'] 和 $db['write']),它只加载 $db['default']。真正生效的方式是:在 .env 文件里用点号语法定义两组独立配置。
-
database.read.hostname = mysql-slave(注意不是localhost,容器环境必须填真实服务名) -
database.read.username = app_ro(只读账号,无写权限,防误操作) database.write.hostname = mysql-masterdatabase.write.username = app_rw- 两个连接的
database名建议一致,否则跨库 JOIN 或迁移脚本会出错 - 别设
database.default.*—— CI4 会优先读它,覆盖你其他配置
怎么让 Model 显式调用不同连接而不污染逻辑?
别改 $this->db,也别在构造函数里硬编码连接名。推荐用参数注入 + 属性隔离:
- 在 Model 构造函数里接收连接名:
public function __construct(string $connection = 'write') - 内部统一用
$this->db = \Config\Database::connect($connection) - 读操作方法显式传
'read':new UserModel('read')->find(123) - 写操作保持默认:
new UserModel()->insert($data) - 连接对象命名要明确,比如
$this->readDB和$this->writeDB,避免用$db1这类模糊变量
事务或强一致性读必须走主库,怎么强制?
CI4 没有内置“读主库”标记,得自己加轻量判断。常见错误是用户注册后立刻查邮箱返回 null——从库延迟导致。
- 不要靠
sleep()补救,延迟不可控 - 用 session 或 context 标记:
session()->set('force_master', true) - 在自定义连接获取逻辑里检查该标记,命中则跳过
'read'分组,直连'write' - 事务内所有查询(含
SELECT ... FOR UPDATE)必须走主库,否则可能触发Deadlock found when trying to get lock - 别在 Model 构造函数里做连接选择——此时还不知道后续是读是写;判断必须落在具体方法入口
为什么不能用 Database::connect() 全局调用?
一旦执行 $db = \Config\Database::connect(),CI4 就会按 default 组初始化连接,之后所有未指定分组的操作都走这个单例,彻底绕过读写分离。
- 禁用所有类似写法:
\Config\Database::connect()、DB()、service('database') - 所有连接必须带分组名:
\Config\Database::connect('read')或\Config\Database::connect('write') - CI4 内部有连接池,重复调用
connect('read')不会新建连接,不必担心性能 - 错误堆栈里能直接看到来自
read还是write,排查问题更准
SELECT status,也得强制主库。这点容易被忽略,但线上故障往往就出在这儿。

















