usermodel 总走 default 连接,因 ci4 basemodel 构造时强制调用 database::connect() 无参版本返回 default,且 $this->db 在父类中 final 初始化无法覆盖;须在方法内显式调用 service('readdb') 或 database::connect('readonly') 切换连接。

CI4 中自定义类模型(如 UserModel)与自定义数据库连接(如 ReadConnection、WriteConnection)发生冲突,本质是类加载路径和连接实例绑定混乱,不是命名重复问题,而是连接路由逻辑被绕过或覆盖。
为什么 UserModel 总走 default 连接,不认你写的 ReadConnection?
CI4 的 BaseModel 在构造时默认调用 \Config\Database::connect(),而该方法无参数时强制返回 default 组配置——哪怕你已注册了 read 和 write 服务,模型也不会自动感知。更关键的是:$this->db 属性在父类中已被初始化完毕,子类无法在构造函数里“换掉”它。
- 不要在
UserModel::__construct()里手动重新赋值$this->db,CI4 会忽略(属性已被 final 初始化) - 不要依赖
protected $DBGroup = 'slave',这个只对\Config\Database::connect($group)显式调用生效,不影响BaseModel自动连接逻辑 - CI4 不会扫描服务容器里叫
read或write的服务并自动注入;它只认default,除非你显式干预
如何让 UserModel 真正用上自定义连接类?
必须放弃“模型自动选连接”的幻想,改用方法级显式连接切换。核心是:把连接选择逻辑下沉到每个读/写方法内部,而不是试图改造模型基类或覆盖属性。
- 在
UserModel中声明protected $useMaster = false,作为运行时标记 - 重写
find()、findAll()等读方法,内部用\Config\Database::connect('readonly')获取从库连接 - 重写
insert()、update()等写方法,强制用\Config\Database::connect('default') - 事务内所有操作(包括
select for update)必须统一走主库,不能靠$useMaster标记,而应检查$this->db->transStatus() === null状态
ReadConnection 类和服务注册后为何仍不生效?
自定义连接类(如 ReadConnection)若只是实现了 ConnectionInterface,但没在服务容器中正确绑定并替代原生 database 服务,它就只是个普通类,不会被框架调用。CI4 的查询构建器(BaseBuilder)只认 $this->db 实例,而该实例来源完全由 BaseModel 构造逻辑控制。
- 不要用
Services::set('database', ...)替换全局database服务——这会破坏所有其他模型的默认行为 - 应在
app/Config/Services.php中新增一个工厂方法,例如public static function readDb(),返回new ReadConnection(...) - 在模型方法中调用
service('readDb')或\Config\Services::readDb(),而非尝试注入或覆盖$this->db - 注意:自定义服务名(如
readDb)不能与 CI4 内置服务名(database、cache)冲突,否则会被静默覆盖
最易被忽略的隔离点:事务上下文穿透
即使你在 find() 中用了从库连接,一旦当前请求中某个地方开启了事务(比如另一个模型调用了 $db->transStart()),后续所有数据库操作都会复用那个事务连接——此时 UserModel->find() 实际走的仍是主库,且毫无提示。
- CI4 没有跨连接事务支持,
transStart()只绑定单个连接实例 - 不要在控制器中混合使用
$db->transStart()和$userModel->find(),除非你确认两者共用同一连接 - 强一致性读场景(如刚写入立刻查)必须跳过所有从库逻辑,直接用
\Config\Database::connect('default')并关闭缓存 - 日志或调试时打印
get_class($this->db)和$this->db->getPlatform(),比看配置文件更可靠











