ci4不支持自动读写分离,强一致性读必须手动指定主库连接、禁用缓存并全程复用同一连接实例,避免默认db实例或缓存导致读取旧数据。

CodeIgniter 4(CI4)本身不内置读写分离能力,强一致性读必须靠应用层显式控制连接路由——不是配个 read_only 就能自动切主,也不是加个注解就万事大吉。
CI4 中如何手动指定主库连接执行 SELECT
CI4 的 $db 实例默认走配置的主连接(default),但如果你启用了多数据库配置(如 write 和 read),就必须在需要强一致性的查询前主动切换连接。
- 确保
app/Config/Database.php中定义了至少两个组:一个write(主库)、一个或多个read(从库) - 用
\Config\Database::connect('write')显式获取主库连接,而不是依赖service('database') - 不要在事务外用
DB()或model->db,因为它们默认可能绑定到read组(取决于你是否重写了Database工厂逻辑) - 示例:
$db = \Config\Database::connect('write'); $result = $db->table('orders')->where('id', $id)->get()->getRow();
事务内 SELECT FOR UPDATE 必须绑定主库连接
CI4 的 $this->db->transStart() 不会自动锁定连接路由。一旦你在事务中混用 DB()(指向从库)和手动 connect('write'),就会出现锁等待超时或静默读旧数据。
- 事务开始前,必须先拿到主库连接,并全程复用它:
$db = \Config\Database::connect('write'); - 所有
insert/update/delete/select操作都调用该$db实例,包括select * from t where id=1 for update - CI4 的 Query Builder 不校验 SQL 类型,
->where()->get()后加FOR UPDATE需手动拼接:$db->query("SELECT * FROM orders WHERE id = ? FOR UPDATE", [$id]) - 若用 Model,需重写
$this->db属性:$this->db = \Config\Database::connect('write');,否则$this->find()仍走默认连接
避免被 CI4 的 Database Cache 或 Query Caching 干扰强读
CI4 的缓存机制(如 cacheOn() 或全局 cache config)默认对所有连接生效。如果缓存键没区分主/从库,从缓存读到的可能是旧数据,甚至跨库污染。
- 强一致性场景下,务必禁用当前查询的缓存:
$db->cacheOff()->table(...)->get() - 不要在
app/Config/Cache.php中为数据库缓存启用全局开关;更稳妥的是只对明确无一致性要求的报表类查询开启 - 注意 CI4 的
Query缓存是基于 SQL 字符串哈希的,SELECT /*+READ_CONSISTENCY(WEAK)*/这类 Hint 会改变哈希值,但 CI4 原生不支持 OceanBase 的 Hint 写法,需手写原生查询
中间件或代理无法替代应用层路由判断
即使你用了 ProxySQL 或 ShardingSphere,CI4 发出的请求仍可能被错误路由——因为这些中间件通常只看 SQL 关键字(如 SELECT),不解析事务上下文或锁提示。
- ProxySQL 默认规则匹配
^SELECT就发给从库,SELECT ... FOR UPDATE会被当成普通读,除非你额外配置mysql_query_rules正则匹配FOR UPDATE - ShardingSphere 的
hint路由需客户端显式调用HintManager,CI4 没有集成 SDK,只能靠 HTTP Header 或自定义中间件透传 - 最简方案仍是:强一致性读 = 主动选主库连接 + 禁用缓存 + 避免复用默认 DB 实例
真正容易被忽略的点是:CI4 的 Model、Builder、Seeder、Migrations 全部共享同一套连接工厂逻辑,一旦某处误配了 DB(),就可能把强一致性查询悄悄打到从库——这种问题不会报错,只会间歇性返回旧数据,排查成本远高于提前约定好连接使用规范。











