ci4数据库配置仅识别.env文件,忽略app/config/database.php;需正确设置database.default.*四行参数,并注意dsn覆盖、环境变量及手动实现读写分离。

CI4 的数据库配置只认 .env,不读 app/Config/Database.php
这是 CI4 和 CI3 最容易踩坑的差异点。CI3 会从 app/config/database.php 加载配置,而 CI4 完全忽略该文件——哪怕你改得再对,也毫无作用。项目启动时若报 Database connection failed,90% 是因为没正确设置 .env。
必须执行:cp .env.example .env,然后确保以下四行非空且无拼写错误:
database.default.hostname = "127.0.0.1"database.default.username = "root"database.default.password = "yourpass"database.default.database = "myapp"
注意:database.default.DSN 若存在,会覆盖以上字段;调试阶段建议删掉或注释它。另外,CI_ENVIRONMENT=development 要设好,否则连接失败时只报白屏,看不到具体错误。
CI4 不支持原生读写分离配置,需手动切换 $db 实例
CI4 的核心类库(如 BaseModel)默认只绑定一个 $db 实例,即 database.default。它没有像 CI3 那样内置 $this->load->database('read') 或 $this->load->database('write') 的多组配置加载机制。
要实现读写分离,实际做法是:
- 在
.env中定义两套配置,例如:database.read.hostname、database.write.hostname - 在需要读操作的地方,显式获取读库:
$dbRead = \Config\Database::connect('read') - 在需要写操作的地方,显式获取写库:
$dbWrite = \Config\Database::connect('write') - 所有模型方法(如
find()、insert())必须传入对应实例,不能依赖全局$this->db
这意味着你无法直接继承 BaseModel 并自动走读库——除非重写其构造逻辑,或封装一个 ReadOnlyModel 类,在 __construct() 里强制指定 $this->db = \Config\Database::connect('read')。
从 CI3 迁移时,别试图复用 database.php 配置结构
CI3 的 database.php 支持数组嵌套、多组配置、自动 fallback,比如:
$db['default'] = [...]; $db['read'] = [...]; $db['write'] = [...];
CI4 的 .env 是扁平键值对,不支持嵌套或条件判断。你不能写 database.read.hostname = "slave1" 然后期望框架自动识别“read”为独立组——必须配合代码调用 \Config\Database::connect('read') 才能生效。
迁移时常见错误包括:
- 把 CI3 的
$active_group = 'read'直接照搬进 CI4,结果完全无效 - 在 CI4 模型里继续用
$this->load->database('read', TRUE),该方法在 CI4 中已不存在 - 误以为只要在
.env里写了database.read.*就能自动路由读请求,其实没有任何自动分发逻辑
读写分离的事务与一致性风险比想象中高
CI4 默认不提供跨库事务支持。如果你在写库执行 insert() 后立刻去读库查刚插入的数据,大概率查不到——主从延迟、binlog 传输、relay log 应用都有时间差。这不是框架问题,而是 MySQL 架构本身限制。
真正需要强一致性的场景(比如支付成功后立即展示订单),必须强制走写库查询,例如:
$db = \Config\Database::connect('write');
$order = $db->table('orders')->where('id', $id)->get()->getRow();
不要为了“读写分离”而牺牲业务正确性。很多团队上线后才发现:所谓“读库分担压力”,在中小流量下收益极小,反而因延迟引发用户投诉。先压测、再拆分,比一上来就配 read/write 更实在。











