codeigniter 4 默认不支持数据库读写分离,因 database.php 仅用于类型提示,实际连接全由 .env 的 database.default.* 配置驱动,且 database::connect() 硬编码只支持 default 组;需手动实现 databasemanager 封装读/写连接,并在业务层按操作类型显式切换,同时处理主从延迟与故障降级。

CodeIgniter 4 默认不支持数据库读写分离,Database.php 被完全忽略,所有连接都从 .env 加载——这意味着你不能靠改配置文件直接定义 read 和 write 两组连接,必须手动干预连接初始化流程。
CI4 中为什么不能像 CI3 那样配多个 database group?
CI4 的 \Config\Database::connect() 内部只认 database.default.* 这一套配置,且硬编码了 group 名为 default。即使你在 .env 里写 database.read.hostname,框架也不会自动识别或加载它——这些键名不会被解析,更不会注册为可用连接实例。
- CI3 的
$db['read']是数组结构,由Loader动态加载;CI4 的Database类是单例 + 静态工厂,只初始化一次default -
app/Config/Database.php在 CI4 中仅用于类型提示和 IDE 支持,实际运行时完全不读取 - 试图在控制器里多次调用
\Config\Database::connect('read')会失败,因为该方法不接受参数,且底层没有维护多组连接池
如何手动创建并切换读库/写库连接?
必须绕过框架默认连接逻辑,自己用 mysqli 或 PDO 实例化连接,并封装成可复用的服务类。推荐做法是写一个 DatabaseManager,按需返回不同连接:
- 在
app/Services/DatabaseManager.php中定义两个静态方法:getWriteConnection()和getReadConnection() - 每个方法内调用
new \mysqli($host, $user, $pass, $db, $port),参数从.env单独读取(如database.write.hostname) - 确保
.env中显式声明两套配置,例如:database.write.hostname = 10.0.1.5database.read.hostname = 10.0.1.6database.write.database = myapp_maindatabase.read.database = myapp_slave - 不要复用同一个连接对象做读写混用,否则可能因事务状态或字符集不一致导致异常
在模型或服务中安全调用读/写连接
CI4 的 Model 类默认绑定 default 连接,无法自动适配读写分离。你需要显式传入连接对象,或重写模型构造逻辑:
- 避免继承
CodeIgniter\Model后直接调用$this->db——它永远指向default - 在自定义模型中接收连接实例:
public function __construct(?\mysqli $conn = null) { $this->conn = $conn ?? service('database')->getConnection(); } - 业务层判断操作类型:
if (in_array($method, ['insert', 'update', 'delete'])) { $db = DatabaseManager::getWriteConnection(); } else { $db = DatabaseManager::getReadConnection(); } - 注意:
Query Builder不支持非默认连接,所有查询必须手写 SQL 或用原生mysqli_query()
容易被忽略的坑:主从延迟与事务一致性
读写分离不是加个从库就完事。CI4 没有内置事务传播机制,一旦你在写操作后立刻查从库,大概率读到旧数据。
- 写操作完成后,不要立即用
getReadConnection()查询刚插入的数据;要么强制走主库查(getWriteConnection()),要么加SLEEP(0.1)(仅测试用) - 跨库事务不存在——CI4 不支持分布式事务,
START TRANSACTION只对单个连接生效,无法跨write和read连接协调 - 从库宕机时,
getReadConnection()应该有 fallback 逻辑(比如降级回主库读),否则整个读请求链路会崩











