读写分离在CI3中需显式定义两个独立数据库配置(如default和writedb),Model中分别加载并严格区分使用:读操作用$this->db,写操作必须用$this->writedb,避免混用导致静默失败或ID获取错误。

$db['default'] 是只读库,$db['writedb'] 是可写库——这是 CI3 实现读写分离最直接、也最稳妥的方式。它不依赖自动路由或中间件,靠的是你在 Model 里**显式选择用哪个连接对象**。
怎么在 config/database.php 里定义主从两套配置
必须为两个库使用不同键名(比如 default 和 writedb),不能共用同一个键再靠开关切换。CI3 不支持运行时动态切组,只支持按名加载。
-
$db['default']配只读从库:hostname 填从库 IP,db_debug可设为FALSE,pconnect建议TRUE减少连接开销 -
$db['writedb']配主库:hostname 必须是主库 IP(不能是 localhost,否则高可用失效),db_debug开发期建议TRUE,方便定位写失败原因 - 两个库的
char_set和dbcollat必须一致,否则跨库 JOIN 或数据迁移时可能乱码;推荐统一用utf8mb4和utf8mb4_unicode_ci - 别漏掉
dbdriver:CI3 的mysqli驱动不兼容 MySQL 8+ 默认的caching_sha2_password,主库若为 MySQL 8+,得先执行ALTER USER 'user'@'%' IDENTIFIED WITH mysql_native_password BY 'pass';
Model 里怎么安全地调用读写库
不能只靠 $this->db,必须显式加载并保存为独立属性,否则写操作误走 default 会静默失败(尤其在事务或批量插入后查 ID 时)。
- 在 Model 构造函数中加载:
$this->db = $this->load->database('default', TRUE);用于读,$this->writedb = $this->load->database('writedb', TRUE);用于写 - 写操作必须用
$this->writedb->insert()、$this->writedb->update(),不能混用$this->db - 刚写完立刻要取 ID?必须用
$this->writedb->insert_id(),用$this->db->insert_id()肯定返回 0——因为不是同一个连接对象 - 如果某个 Model 只读不写,就只加载
default;如果只写不读(如日志写入),就只加载writedb,省资源
为什么 $this->db->query() 成功但后续操作报错
常见于把读写混在同一个方法里,或者没注意 CI3 的连接对象隔离机制。一旦你用了 $this->load->database('writedb', TRUE),返回的对象和 $this->db 完全无关。
- 错误写法:
$this->db->insert(...); $id = $this->db->insert_id();—— 如果$this->db指向的是default,insert 其实根本没执行 - 正确写法:
$this->writedb->insert(...); $id = $this->writedb->insert_id();,且这两行必须紧挨着,中间不能穿插SELECT或其他查询 - 事务场景更危险:CI3 的事务方法(
trans_start())只作用于当前 DB 对象,$this->writedb->trans_start()对$this->db无效 - 调试技巧:在关键位置加
var_dump($this->writedb->hostname);确认当前对象连的是哪台机器
上线前最容易被忽略的三个点
配置写对只是第一步,生产环境的真实瓶颈往往藏在连接管理和时序逻辑里。
- 从库延迟没监控:主库写完立刻去从库查,可能查不到最新数据。业务上必须接受“最终一致性”,或加 sleep(1) 强制等待(仅限低频场景)
- 没关
save_queries:开发期开着方便 debug,但上线后每个查询都会被记录到内存,QPS 高时直接拖垮 PHP 进程 - 忘记改
ENVIRONMENT:.env 里还是development,导致db_debug=TRUE暴露敏感信息,且错误页可能泄露主库 IP


















