根本原因是路由逻辑不一致:写入与查询分片键不同、CI不感知分片导致查询发错库;需显式提取分片键、统一生成库表名、动态切换连接、避免跨库JOIN、封装分片库时正确注入DB实例,并注意事务边界。

分库分表后 SELECT 查询为什么总查不到数据?
根本原因往往是路由逻辑没对齐:你在写入时按 user_id % 4 落库,但查询时却用 order_id 去算分片,或者没把分片键传进查询上下文。CI(CodeIgniter)默认不感知分片,所有查询都发给主库或第一个分库,自然漏数据。
实操建议:
- 必须在模型方法入口处显式提取分片键(如
$shard_key = $data['user_id'] ?? $this->input->get('uid');),不能依赖全局变量或 session - 禁止在
where()里混用多个分片键(比如同时传user_id和tenant_id却没定义优先级),CI 的 Active Record 不会自动推导路由规则 - 分库名和分表名必须由同一套函数生成,例如统一用
get_db_name($shard_key)和get_table_name($shard_key),避免写库用md5、读库用%
如何让 CI 的 $this->db 动态切换到目标分库?
不能靠 $this->load->database('shard_2') 预加载——那只是配置别名,不是运行时路由。真正生效的是 $this->db->conn_id 指向的 PDO 实例,得在查询前重置连接。
实操建议:
- 封装一个
switch_shard($shard_key)方法,在其中调用$this->db->close(),再用mysqli_connect()或PDO手动连目标库,最后把新连接赋给$this->db->conn_id - CI 3.x 中不要修改
$this->db->hostname后调reconnect(),该方法只重连不切换数据库,且可能复用旧连接池 - 如果用了 PDO,务必在
switch_shard里设置PDO::ATTR_EMULATE_PREPARES => false,否则预处理语句可能跨库执行失败
JOIN 查询跨分片时为什么报错或结果异常?
CI 的 Active Record 不支持跨库 JOIN,$this->db->join() 生成的 SQL 默认都在当前连接库内找表。若你手动拼 db1.table_a JOIN db2.table_b,MySQL 会拒绝(除非开启 federated 引擎,但生产环境几乎不用)。
实操建议:
- 放弃跨分片 JOIN,改用应用层组装:先查主表分片数据,提取关联 ID 列表,再根据这些 ID 路由到对应分片查副表,最后 PHP 合并
- 如果必须 JOIN,只允许同库不同表(如
shard_1.user_001 JOIN shard_1.order_001),且两个表的分片键必须一致,否则数据错位 - 警惕 CI 的
get_compiled_select():它不展开数据库前缀,生成的 SQL 里还是{prefix}user,需手动替换为真实库名+表名
自定义分片规则封装成 CI 库要注意什么?
别写成独立 Library 加载就完事——CI 的 load->library() 不会自动注入 $this->db,你得手动传参或在构造函数里取 get_instance(),否则路由逻辑里根本拿不到 DB 实例。
实操建议:
- 库类构造函数必须接收
&$ci参数,并保存为属性:$this->ci =& $ci;,后续通过$this->ci->db访问连接 - 分片函数(如
hash_shard($id, $n = 8))要支持可配置参数,不能硬编码4或16,否则扩容时要改全量代码 - 所有对外方法返回值统一为数组:
['db' => 'shard_3', 'table' => 'user_07', 'where' => [...]],方便上层决定是走 Active Record 还是原生query()
最易被忽略的是事务边界:跨分片 UPDATE 必须用 XA 或最终一致性补偿,CI 的 $this->db->trans_start() 只作用于单个连接,切库后事务就断了。

















