ci4读写分离需手动配置主从数据库组并显式调用,禁用从库持久连接,事务内读操作必须走主库,连接复用依赖框架缓存而非外部连接池。

CI4 读写分离必须绕过 .env 单点配置
CodeIgniter 4 官方不支持开箱即用的读写分离,.env 文件只允许定义一个默认数据库组(database.default.*),无法原生声明主库/从库多个连接组。硬要在 .env 里写 database.master.* 和 database.slave.* 是无效的——CI4 的 \Config\Database::connect() 默认只认 default,其他组名不会被自动加载。
可行路径只有一条:手动注册多个数据库配置,并在业务逻辑中显式调用。例如:
$db_master = \Config\Database::connect('master');
$db_slave = \Config\Database::connect('slave');
这就要求你:
- 在
app/Config/Database.php中定义public $master和public $slave两个完整配置数组(不是注释掉的示例) - 确保
.env中的CI_ENVIRONMENT = development已启用,否则连接失败时无错误提示 - 避免在 Migration 或 Seeder 中混用主从连接——这些工具默认只走
default组,强行切换可能引发事务异常
从库连接必须禁用 persistent 连接
MySQL 的持久连接(persistent => true)在读写分离场景下极易引发状态污染:一个请求从从库查完数据后,连接未释放,下次被复用时若恰好路由到主库执行写操作,可能因字符集、SQL mode、autocommit 等会话变量不一致导致报错,典型如 ERROR 1235 (42000): This version of MySQL doesn't yet support 'MULTI DELETE' 这类看似无关的语法错误。
正确做法是为从库连接显式关闭持久化:
public $slave = [
'DSN' => '',
'hostname' => '192.168.10.20',
'username' => 'reader',
'password' => 'r34d0nly',
'database' => 'myapp_db',
'DBDriver' => 'MySQLi',
'DBPrefix' => '',
'pConnect' => false, // 必须设为 false
'DBDebug' => true,
// ... 其他项
];
主库可保留 pConnect => true(视负载而定),但从库一律禁用。这是 CI4 + MySQL 读写分离中最常被忽略的稳定性开关。
HikariCP 不适用 CI4,改用连接池复用需手写封装
CI4 自带的数据库层不兼容 HikariCP、Druid 等 Java 风格连接池,也没有内置连接池管理器。所谓“连接复用”,在 CI4 中实际指两件事:一是复用已建立的 PDO/MySQLi 实例,二是控制连接生命周期。前者靠框架自身的静态连接缓存实现(\Config\Database::connect() 多次调用同名组会返回同一实例),后者必须靠人工干预。
关键控制点:
- 每次查询结束后,不要调用
$db->close()—— 这会主动销毁连接,失去复用意义 - 在控制器方法末尾不显式断开,让 PHP 请求结束时由 MySQLi 自动回收(前提是未启持久连接)
- 若需强制释放,用
mysqli_close($db->connID),但仅限极少数长耗时脚本,日常无需干预 - 真正影响复用效率的是
app/Config/Database.php中的DBDebug和failover设置:开启DBDebug = true会额外记录堆栈,轻微拖慢连接获取;failover数组若配置了备用主机,每次 connect 都会尝试连接列表中所有地址,显著增加延迟
事务内读操作必须强制走主库
这是读写分离最易踩的业务逻辑坑。比如用户刚提交订单(INSERT 到主库),紧接着在同一个事务里调用 $db_slave->table('orders')->where('id', $id)->get(),大概率查不到刚插入的数据——因为从库存在复制延迟,且 CI4 不会自动识别“此读应等主库同步后再发”。
解决方案只有两种:
- 事务中所有数据库操作,统一使用
$db_master实例,完全避开从库 - 非事务场景下,对“写后立即读”的关键路径,加注释提示中间件(如果你用了 ProxySQL/MaxScale):
SELECT /*+ FORCE_MASTER */ * FROM orders WHERE id = ?,但注意 CI4 的 Query Builder 不会透传这类 hint,必须用$db_master->query()原生执行
没有银弹。复制延迟(Seconds_Behind_Master)是 MySQL 主从的固有特性,框架层无法消除,只能靠业务代码规避。











