不能直接修改 config/database.php 中的 default 配置,因为该文件在框架启动时一次性加载并固化为全局对象,后续修改 $db['default'] 不影响已实例化的 $this->db;且多租户需请求级连接隔离,复用同一实例会导致数据污染或争用。

CodeIgniter 多租户系统不能靠改 $db['default'] 静态配置来切换数据库——必须在运行时用 $this->load->database() 加载新连接,并显式传入完整配置数组。
为什么不能直接修改 config/database.php 中的 default 配置
CodeIgniter 的 database.php 是启动时一次性加载的,$db['default'] 被读取后就固化为全局对象;后续修改该数组不会影响已实例化的 $this->db。更关键的是,多租户场景下每个请求需隔离连接,复用同一 $this->db 实例会导致跨租户数据污染或连接池争用。
- 修改
$db['default']后不 reload,$this->db仍是旧连接 - 强行
unset($this->db)再$this->load->database()易引发单例失效、事务中断 - 没有租户上下文隔离,无法支持并发请求访问不同数据库
正确做法:用 $this->load->database() 动态加载连接
每次需要操作租户专属库时,调用 $this->load->database() 并传入完整配置数组,返回独立的 DB 实例。该实例与默认 $this->db 完全无关,可安全用于当前请求上下文。
- 配置数组必须包含全部键:如
'hostname'、'username'、'database'(值为租户库名)、'dbdriver'等,不能只改'database' - 建议封装成辅助函数,例如
get_tenant_db($tenant_id),内部根据租户 ID 查询元数据库获取对应库名和凭证 - 避免在模型中硬编码 DSN 或拼接字符串——应从可信来源(如元数据库或加密配置中心)获取凭据
租户标识解析时机与连接复用策略
租户 ID 必须在控制器逻辑前就确定,否则无法提前加载对应数据库。常见来源包括子域名、X-Tenant-ID 请求头、JWT claim 或路由参数(如 /t/{tenant}/api)。解析后应缓存到请求生命周期内可用的位置(如 CI 的 $this->session 或自定义 TenantContext 类),而非每次重复解析。
- 子域名方式:在
application/core/MY_Controller.php构造函数中用$_SERVER['HTTP_HOST']提取并校验,失败立即show_404() - 请求头方式:中间件级校验(CI3 无原生中间件,可用钩子
pre_system或基类构造函数拦截) - 连接不应复用:每个租户应持有独立
$tenant_db实例;不要把动态 DB 实例赋给$this->db覆盖默认连接,防止后续日志、缓存等模块误用
实际使用中容易忽略的关键点
动态连接看似简单,但漏掉任一环节都可能导致连接泄漏、权限错误或 SQL 注入。最常被跳过的其实是租户 ID 的可信性校验和数据库名白名单约束。
- 租户 ID 直接拼进
'database'值?必须先通过正则过滤(如/^[a-z0-9_]{2,32}$/i)或查元数据库确认存在,否则可能被利用执行USE `; DROP DATABASE xxx; --` - 没设
'pconnect' => FALSE?长连接在多租户下极易复用错库,尤其当 Apache 使用 prefork MPM 时 - 没关
'db_debug'?租户库连接失败时暴露完整 DSN 和凭证,属于高危信息泄露
动态连接不是“换个 database 名字就行”,而是要确保租户上下文可信、连接实例隔离、配置项完整且安全。一旦租户 ID 来源不可控,所有后续操作都建立在沙堆之上。











