codeigniter 3 不内置读写分离,需手动配置多数据库并在 model 中显式选择连接,或重写 loader::database() 实现自动 sql 路由;推荐在 model 构造函数中分别加载从库(default)和主库(writedb),select 走 $this->db,insert/update/delete 及事务必须走 $this->master,且禁用 autoinit 避免无谓连接。

CodeIgniter 3 本身不内置读写分离逻辑,必须靠手动配置多数据库 + 在 Model 中显式选择连接,或重写 DB 加载机制来实现自动路由。没有“开箱即用”的 read/write 键配置,这点和 Laravel 不同。
在 application/config/database.php 中定义主从连接
CI3 的数据库配置支持多组连接,只需给每组起不同键名即可。关键不是叫 default 或 writedb,而是后续代码里能准确引用它。
-
default可以指向从库(用于常规SELECT),但不要设'autoinit' => TRUE,避免无谓连接 - 单独加一组如
writedb,指向主库 IP 和账号,确保有写权限 - 所有连接必须保持
dbdriver一致(如都用mysql),否则 CI 的 Query Builder 会出兼容问题 - 从库建议在 MySQL 配置中启用
read_only = ON,防止误写,CI 层不拦截INSERT到从库的调用
示例片段:
$db['default'] = array( 'hostname' => '192.168.1.10', // 从库 'username' => 'slave_user', 'password' => 'xxx', 'database' => 'myapp', 'dbdriver' => 'mysql', 'autoinit' => FALSE, // 关键:不自动连 ); $db['writedb'] = array( 'hostname' => '192.168.1.5', // 主库 'username' => 'master_user', 'password' => 'xxx', 'database' => 'myapp', 'dbdriver' => 'mysql', 'autoinit' => FALSE, );
在 Model 中手动切换连接(最简单、最可控)
这是 CI3 最常用也最不容易翻车的方式。不依赖任何钩子或重写,逻辑清晰,调试方便。
- 每个 Model 构造函数里用
$this->load->database()显式加载两个连接,分别赋给不同属性,比如$this->db(从库)、$this->master(主库) - 所有
SELECT相关方法(get()、get_where()、query("SELECT..."))走$this->db - 所有写操作(
insert()、update()、delete()、query("INSERT..."))必须走$this->master - 事务只能在主库上执行:
$this->master->trans_start(),跨库事务不成立
注意:不能只写 $this->db->insert() 就完事——如果 $this->db 是从库连接,这条语句会静默失败或报错(取决于 MySQL 权限设置)。
重写 Loader::database() 实现自动 SQL 类型路由(进阶,慎用)
想让 $this->db->get() 自动走从库、$this->db->insert() 自动走主库,就得干预 CI 的数据库加载流程。这不是改配置,而是改行为。
- 需新建
application/core/MY_Loader.php,覆盖database()方法,优先加载自定义的DB.php - 再建
application/core/database/DB.php,重写DB()工厂函数,在初始化时根据当前请求上下文或 SQL 前缀决定返回哪个连接实例 - 这种方案容易踩坑:Query Builder 生成的 SQL 可能含子查询或
SELECT ... FOR UPDATE,这类“读中带写”语句若被错误路由到从库,会直接报错 - CI3 的
db->query()不做 SQL 解析,所以你得自己写正则判断开头关键字,但/*+FORCE_MASTER*/这类 hint 无法识别,强一致性读场景难处理
除非团队有长期维护自定义核心组件的能力,否则不建议走这条路。线上环境出问题时,排查链路比直接看 Model 还长一截。
主从延迟导致读不到最新数据怎么办
这是读写分离绕不开的现实问题。CI3 完全不感知复制延迟,它只管发 SQL、收结果。
- 用户刚注册(主库写入),立刻跳转到个人页(从库读取)——可能查不到刚写的记录
- 解决方案不是让框架等,而是业务层控制:对刚写入后立即要读的场景,强制走主库,例如
$this->master->get('users')->row() - 不要依赖
sleep(0.1)或轮询,既低效又不可靠 - 监控
Seconds_Behind_Master是运维责任,但开发得知道这个值存在,并在设计接口时预留“主库直读”开关
真正容易被忽略的,是事务内混用连接。哪怕只有一行 $this->db->select()->from()->get() 写在 $this->master->trans_start() 和 trans_complete() 之间,也会破坏事务原子性——因为那是另一条 TCP 连接,MySQL 根本不认它是同一个事务。











