CodeIgniter 4 需手动配置读写分离:在 app/Config/Database.php 中定义 $db['write'] 和 $db['read'] 独立连接组,模型中通过 \Config\Database::connect('write') 和 ::connect('read') 显式加载并绑定属性,禁止混用事务与跨库查询,灰度发布需结合 ENVIRONMENT 动态覆盖连接参数。

CodeIgniter 4 的 database.php 怎么配读写分离连接组
CI4 不像 CI3 那样原生支持“读库/写库”自动路由,必须手动定义多个连接组并由业务逻辑控制流向。直接在 app/Config/Database.php 中声明至少两个独立连接:
-
$db['write']:指向主库(hostname填主数据库 IP,port显式指定,DB_DEBUG开发环境建议设为TRUE) -
$db['read']:指向从库(注意username权限只能查,不能写;若用同一账号,MySQL 层需限制REPLICATION SLAVE权限外的写操作) - 两个组的
DBDriver、charset、DBCollat必须一致,否则QueryBuilder可能因字符集不匹配报错 - 不要复用
$db['default']名称——它会被 CI4 的自动加载机制优先捕获,干扰灰度逻辑
模型里怎么安全切换读/写连接而不踩坑
CI4 模型中不能靠 $this->db 全局切换,必须显式加载并绑定到局部属性,否则并发下会串库:
- 在构造函数中用
$this->writeDB = \Config\Database::connect('write')加载主库,$this->readDB = \Config\Database::connect('read')加载从库 - 所有写操作(
insert()、update()、delete())只走$this->writeDB;读操作(get()、query())默认走$this->readDB,但需加判断:if (service('request')->getGet('force_write') === '1') { $db = $this->writeDB; } - 避免在同一个事务里混用两个连接——CI4 的
transStart()只作用于当前连接对象,跨库事务无法保证原子性 - 如果用了 Query Builder,记得调用前先
$this->readDB->table('users'),而不是$this->writeDB->table('users'),否则查的是主库
Nginx 灰度规则怎么把流量打到不同数据库配置的实例上
灰度发布本身不改数据库配置,而是让不同实例加载不同的 ENVIRONMENT 或自定义配置标识,Nginx 负责把请求路由过去:
- 在
index.php顶部加判断:if (isset($_SERVER['HTTP_X_GRAY_FLAG']) && $_SERVER['HTTP_X_GRAY_FLAG'] === 'v2') { define('ENVIRONMENT', 'gray'); } - 在
app/Config/Database.php中,根据ENVIRONMENT动态覆盖连接参数:if (ENVIRONMENT === 'gray') { $db['write']['hostname'] = '10.0.1.100'; $db['read']['hostname'] = '10.0.1.101'; } - Nginx 配置里用
map提取灰度标识:map $http_cookie $gray_flag { ~*uid=12345 "v2"; default ""; },再通过proxy_set_header X-Gray-Flag $gray_flag;透传 - 别依赖 User-Agent 或 IP 做灰度分流——移动端 DNS 缓存可能导致同一用户在不同请求间跳变连接组,引发数据不一致
为什么 CI4 的服务容器不能直接注入读写双 DB 实例
CI4 的服务容器(Config\Services)默认只注册一个 database 服务,且是单例。强行注册两个会导致容器无法区分上下文:
- 你可以在
Config/Services.php里加public static function writeDatabase($getShared = true)和public static function readDatabase($getShared = true),但每次调用都会新建连接,失去连接池意义 - 更稳妥的做法是:在基类模型中封装
getWriteDB()和getReadDB()方法,内部用static $instances缓存已连接的对象,避免重复 connect - 如果用了 Doctrine 或 Eloquent 替代原生 DB 类,这套手动读写分离就失效了——它们有自己的连接管理器,得另配代理层
- 最易忽略的一点:CI4 的
Cache服务默认用文件存储,灰度实例若共享缓存目录,get()可能从缓存读到旧数据,掩盖了从库延迟问题


















