必须用db_connect()而非$this->load->database()的场景有五种:框架初始化前(如CLI启动脚本)、需完全隔离连接(主库+从库+日志库独立)、非控制器/模型类中临时查配置、动态数据库配置未预定义、以及需每次全新连接避免缓存复用。

什么时候必须用 db_connect() 而不是 $this->load->database()
db_connect() 是 CI4 引入的底层连接工厂函数,它不依赖 Loader、不走服务容器注册流程,直接返回一个干净的数据库实例。你只有在以下场景才需要手动调用它:
- 需要在框架初始化完成前建立连接(比如 CLI 启动脚本、自定义入口文件、或早期日志写入)
- 要创建完全隔离的连接(例如:主库写 + 从库读 + 日志库独立写),且不能复用任何已注册的服务实例
- 在非控制器/模型类中(如纯 PHP 工具类、DTO、第三方 SDK 封装层)临时查一条配置或校验连接,又不想引入 CI 的完整上下文
- 使用了动态数据库配置(比如用户上传的 DB 凭据),且该配置不在
app/Config/Database.php中预定义
注意:db_connect() 返回的是裸实例,没有自动注入 Query Builder,得自己调 $db->newQuery() 或 $db->table()。
$this->load->database() 在自定义类里为什么容易出错
CI3 的 $this->load->database() 在自定义类中调用时,默认仍返回全局单例(即 $this->db),根本没新建连接。很多人误以为加个参数就能隔离,结果事务串了、字符集乱了、甚至主从切换失效。
常见错误现象:
- 多个服务类同时调
$this->load->database('slave'),但实际共用同一连接,负载没分摊 - 在构造函数里调用,导致类被多次实例化时反复 connect,max_connections 快速打满
- 传了配置数组但漏掉
'return' => TRUE,结果返回的是CI_Loader对象,不是数据库实例
正确做法是:
- CI3:用
$this->load->database($config, TRUE),其中$config必须是完整数组(含hostname、username等),且显式设'return' => TRUE - CI4:改用
\Config\Database::connect('group_name')或db_connect($config),别碰$this->load
CI4 中 db_connect() 和 \Config\Database::connect() 的关键区别
两者都返回数据库实例,但生命周期和缓存策略不同:
-
db_connect($config)每次都新建连接,不缓存,适合一次性操作或严格隔离场景(比如导出任务单独连归档库) -
\Config\Database::connect('group_name')默认启用连接池缓存,相同组名多次调用返回同一个实例,更省资源
性能影响:
- 频繁调
db_connect()会绕过连接复用,增加 handshake 开销,尤其在高并发请求中明显 - 若传入的
$config每次都 new 数组(比如从 session 动态拼接),即使内容一样,CI4 也不会命中缓存,相当于每次都新建连接
所以,除非明确需要“每次全新连接”,否则优先用 \Config\Database::connect('group_name'),并确保组名在 app/Config/Database.php 中已定义。
事务跨连接失效这个坑怎么避
事务只能在同一个数据库连接实例内保证原子性。一旦你在同一个请求里用了两个 db_connect() 实例(比如一个查主库、一个写日志库),它们的 start_transaction()、commit() 互不影响。
典型问题:
- 主库事务回滚了,日志库那条记录还在
- 用
$this->db开了事务,又在 Service 类里用db_connect()插了一条,结果日志没随主事务一起撤回
规避方式:
- 同一业务逻辑里,所有 DB 操作尽量约束在同一个连接实例上
- 如果真要多库协同,改用应用层补偿(比如先写主库,成功后再异步发消息写日志库),别依赖跨连接事务
- 不要在一个方法里混用
$this->db和db_connect(),容易自己都搞不清当前连的是谁
真正难处理的不是怎么连,而是连完之后怎么让多个操作“认得彼此”。连接本身只是起点,状态管理才是复杂点。


















