CodeIgniter 3原生不支持自动SQL路由级读写分离,但可通过配置$db['default'](从库)和$db['write'](主库)并重载MY_Loader::database()方法实现对开发者透明的主从分流,读操作默认走default,写操作自动切至write,无需修改模型代码。

CodeIgniter 3 原生不支持自动 SQL 路由级读写分离,但可通过配置 + 少量扩展实现「对开发者透明」的主从分流:写操作走 $db['write'],读操作默认走 $db['default'](即从库),无需改模型代码。
database.php 中必须定义 $db['write'] 和 $db['default'] 两组配置
CodeIgniter 3 的读写分离依赖 Loader 对 database() 方法的识别逻辑 —— 它会优先尝试加载名为 write 的配置组用于写操作,其余读操作(如 get()、query() 不含 INSERT/UPDATE/DELETE)则 fallback 到 $active_group 指向的组(通常是 default)。
-
$db['default']应指向从库(slave)地址,仅开启读权限 -
$db['write']必须存在,且指向主库(master),哪怕和default是同一台机器(开发环境可复用) - 两者
dbdriver、char_set、dbcollat等关键参数需保持一致,否则连接可能失败 -
failover数组可为default配置多个从库,但 CI 3 不自动负载均衡,只按顺序尝试(第一个连不上才试第二个)
$db['default'] = array(
'hostname' => '192.168.1.10', // 从库 IP
'username' => 'readonly_user',
'password' => 'xxx',
'database' => 'myapp',
'dbdriver' => 'mysqli',
'db_debug' => ENVIRONMENT !== 'production',
'read_only' => TRUE, // 自定义标记,后续扩展可用
);
$db['write'] = array(
'hostname' => '192.168.1.5', // 主库 IP
'username' => 'write_user',
'password' => 'xxx',
'database' => 'myapp',
'dbdriver' => 'mysqli',
'db_debug' => ENVIRONMENT !== 'production',
);
$active_group = 'default';
不修改 system 文件的前提下接管 DB() 加载逻辑
CI 3 的 Loader::database() 默认只认一个配置组,要让它在写时自动切到 write,需重写 system/database/DB.php 的加载入口。但直接改 system 是反模式,正确做法是:
- 在
application/core/MY_Loader.php中重载database()方法 - 检查当前执行的 SQL 是否含写意图(如以 INSERT / UPDATE / DELETE / REPLACE / TRUNCATE 开头)
- 若是,则强制传入
'write'作为 group 参数;否则用默认 group
常见错误现象:Call to undefined function DB() 或始终连到同一库 —— 很可能是没覆盖 Loader,或 MY_Loader 类名/路径不匹配(必须是 MY_Loader extends CI_Loader,且文件放在 application/core/)
注意:query() 手动传 SQL 时,无法靠方法名判断读写,必须解析 SQL 字符串首单词(忽略空格和注释)。简单正则如 /^\s*(INSERT|UPDATE|DELETE|REPLACE|TRUNCATE)/i 即可覆盖 95% 场景。
$this->db 在模型中仍可用,但事务必须显式指定 write
启用读写分离后,$this->db 默认仍是 default 组(即从库),所以:
-
$this->db->insert()这类 Active Record 写操作,不会自动跳转到write—— 因为 AR 层在生成 SQL 前并不知道最终要执行什么 - 必须手动切换:
$write_db = $this->load->database('write', TRUE); $write_db->insert('users', $data); - 事务只能在单一连接上进行,因此
$this->db->trans_start()前必须先确保$this->db已加载write组,否则会报错Transaction not supported in read-only mode或静默失败
容易踩的坑:在事务中混用 $this->db(从库)和 $write_db(主库)—— 这会导致事务失效,且从库无法提交。
Slave 节点延迟导致读取不到最新数据
MySQL 主从复制有天然延迟,尤其在高并发写入后立即读取,常出现「写后读不到」。这不是 CI 配置问题,而是架构约束:
- 不要在写操作后立刻用
$this->db->get()查刚插入的记录(除非加 sleep 或重试) - 对强一致性要求的场景(如用户注册后跳转个人页),应统一走主库读取:
$master = $this->load->database('write', TRUE); $user = $master->where('id', $id)->get('users')->row(); - 可封装一个
get_master()辅助方法,避免散落在各处的硬编码
真正复杂的是「混合事务 + 最终一致性」边界——比如扣库存(主库写)后发消息(从库读订单状态),这里没有银弹,得结合业务容忍度做补偿或延迟读。

















