ci4的database::connect()不能直接切换mysqli驱动连接,因其是单例工厂函数,按组名缓存实例;同一组名始终返回旧连接,而mysqli对象绑定唯一服务端地址与认证上下文,无法运行时切换。

CI 不能靠改配置文件或自动加载来切换 mysqli 驱动连接——必须显式实例化、手动管理连接生命周期,否则会复用错误连接或触发超时失败。
为什么 Database::connect() 不能直接切驱动
CI4 的 Database::connect() 是单例工厂函数,它按组名(如 'default')缓存连接实例;即使你传入不同参数新建一个 mysqli 连接配置,只要组名相同,它就返回已缓存的旧实例。而 mysqli 驱动本身不支持运行时“切换”底层 socket 或 host —— 每个 mysqli 对象绑定唯一服务端地址和认证上下文。
常见错误现象:
- 调用
\Config\Database::connect('slave')后执行查询,实际仍连主库 IP - 两次调用
connect('default')返回同一连接对象,无法隔离事务或字符集设置 - 手动 new mysqli() 后未关闭,导致连接数爆满(尤其在 CLI 或长连接场景)
怎么安全地新建 mysqli 连接并复用
绕过 CI4 的连接池机制,直接使用原生 mysqli 类创建独立连接,再包装成 CI 兼容的 ConnectionInterface 实例。这是唯一可控、可验证的方式。
实操建议:
- 不要在 Model 构造函数里写
$this->db = new mysqli(...)—— 它不兼容 Query Builder,且无法用table()等方法 - 封装一个轻量工具类,例如
RawMysqliFactory,统一处理 host/port/user/db/charset 参数和错误抛出 - 每次 new mysqli 后,立即检查
$mysqli->connect_error,非空则 throw new RuntimeException($mysqli->connect_error) - 若需复用连接(如批量读),把
mysqli实例存在类属性或静态缓存中,但要加isConnected()检查,避免连接断开后继续用 - 明确调用
$mysqli->close(),特别是在 CLI 脚本或异步任务中;Web 请求可依赖 PHP 请求结束自动释放,但不保险
如何让 Query Builder 用上自定义 mysqli 连接
CI4 的 Query Builder 不接受裸 mysqli 对象,必须是实现了 ConnectionInterface 的实例。你可以继承 CodeIgniter\Database\MySQLi\Connection 并重写构造逻辑,把外部传入的 mysqli 句柄注入进去。
关键点:
- 继承时必须重写
__construct(),跳过父类的mysqli_connect()调用,改为接收已有$mysqli实例 - 将传入的
$mysqli赋值给$this->connID属性(这是 Query Builder 查找连接句柄的位置) - 覆盖
initialize()方法,避免重复初始化;里面只做字符集设置($mysqli->set_charset())和选项配置 - 使用时:先 new mysqli → 再 new 自定义 Connection → 最后传给
$builder = $this->db->table(...)->setConnection($customConn)
示例片段:
$mysqli = new mysqli('192.168.10.20', 'reader', 'r34d0nly', 'myapp_db', 3306);
if ($mysqli->connect_error) {
throw new RuntimeException('Slave connect failed: ' . $mysqli->connect_error);
}
$slaveConn = new MyCustomMySQLiConnection($mysqli); // 自定义类
$builder = $this->db->table('users')->setConnection($slaveConn)->get();
容易被忽略的字符集与超时陷阱
原生 mysqli 初始化后默认字符集是 latin1,即使你在 DSN 或配置里写了 utf8,也不生效;Query Builder 执行 SET NAMES 前,所有字符串字段都可能乱码。连接超时更隐蔽:PHP 的 default_socket_timeout 对 mysqli 连接建立阶段无效,必须靠 mysqli_options($mysqli, MYSQLI_OPT_CONNECT_TIMEOUT, 3) 显式设置。
必须做的几件事:
- new mysqli 后立刻调用
$mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 3)和$mysqli->options(MYSQLI_OPT_READ_TIMEOUT, 5) - 紧接着调用
$mysqli->set_charset('utf8mb4'),别信配置里的char_set - 如果连接用于读从库,务必确认从库 MySQL 的
wait_timeout和interactive_timeout设置合理(建议 ≥ 300),否则空闲连接会被服务端主动断开 - 不要在同一个
mysqli实例上混用query()和prepare()—— mysqli 内部状态不一致可能导致后续 prepare 失败,报错Commands out of sync
复杂点在于:这些设置无法通过 CI4 的配置数组透传,只能在 new mysqli 之后、注入到 Connection 之前完成。漏掉任何一项,线上就可能出乱码、超时假死、或偶发连接中断。











