SQL 分库分表查询策略实践

1次阅读

分库后join查不到数据,因跨库join被架构禁止,sql只发往单一分片;count(*)跨分片不准,需显式启用聚合;分表键缺失导致全库扫描;insert…select跨分片通常失败。

SQL 分库分表查询策略实践

分库后 JOIN 查不到数据,是因为根本没在同一个库执行

跨库 JOIN 在绝大多数分库中间件(如 ShardingSphere、MyCat)里默认不支持,不是配置问题,是架构层面被禁止的。数据库连接只发往一个分片,另一个库的表压根不在当前连接上下文中。

常见错误现象:table 'db2.user_order' doesn't exist 或查出空结果但单表能查到数据;用 EXPLAIN 会发现只走了其中一个库的执行计划。

  • 必须拆成多次查询:先查主表(如 user),拿到分片键值(如 user_id),再按路由规则拼出目标库名,查关联表(如 order
  • 如果关联字段不是分片键(比如用 order_no 关联 user_id),就得走全局广播查询或冗余字段(如把 user_name 冗余进 order 表)
  • ShardingSphere 的 Broadcast Table 只适用于小而慢变的维度表(如 dict_type),别硬套在业务主表上

SELECT COUNT(*) 跨分片不准,因为没合并结果

分库后,COUNT(*) 默认只在单个分片执行,返回的是该分片数量。中间件不会自动求和,除非显式启用聚合能力且 SQL 写法合规。

使用场景:后台分页总数、数据量大盘监控——这类地方最容易踩坑,前端显示“共 12 条”,实际有上千条。

  • ShardingSphere 需开启 sql-show: true 并观察日志,确认是否生成了 SELECT COUNT(*) FROM t_order AS t_order_0 union ALL SELECT COUNT(*) FROM t_order AS t_order_1 这类语句
  • 避免写 SELECT COUNT(*) FROM t_order WHERE status = ? GROUP BY user_id —— 分组 + 跨分片 count 几乎必然不支持
  • 对精度要求不高的场景,可用 SHOW TABLE STATUS 各分片行数估算,但注意 InnoDB 的 rows 是估算值,误差可能达 50%

分表键选错导致 WHERE 条件无法下推,全库扫描

分表键(sharding key)决定数据路由。如果 WHERE 条件里没有它,中间件无法判断查哪个表,只能把 SQL 发给所有子表,性能断崖式下跌。

典型表现:原本毫秒级查询变成秒级,SHOW PROCESSLIST 看到大量连接卡在 Sending data,慢日志里出现几十个 t_order_001t_order_099 的重复执行。

  • 高频查询字段优先设为分表键,比如订单查询多按 user_id,就别用 order_time 当分表键
  • 复合分表键(如 [user_id, order_time])要确保查询条件至少命中前缀,WHERE order_time > '2024-01-01' 依然会扫全表
  • 想支持多维度查询?加覆盖索引不行,得建影子表(如按 order_no 分的另一套表),或引入 elasticsearch 做异构索引

INSERT ... SELECT 跨分片失败,中间件通常直接拒绝

这类语句天然涉及源表和目标表的跨库/跨表定位,ShardingSphere 从 5.0 开始才有限支持,且要求源表和目标表在同一逻辑库、分片规则兼容。多数生产环境直接报 UnsupportedOperationException

使用场景:批量导入、报表归档、冷热分离迁移——这些操作一旦卡住,容易引发上游重试风暴。

  • 绕过方案:先 SELECT 出数据(注意内存溢出风险),在应用层按目标分片规则分组,再逐批 INSERT
  • 如果源表本身也分库,必须先做 UNION ALL 汇总,再分发,中间不能有聚合函数(如 MAX())、LIMIT 或子查询
  • 别依赖 REPLACE INTOINSERT IGNORE 的原子性——分片环境下,唯一键冲突检测只在单表生效,跨分片重复插入可能成功两次

分库分表不是加个中间件就完事,每个查询背后都藏着路由决策。最常被忽略的,是那些看起来“应该能跑”的 SQL——它们往往在测试环境安静运行,上线后突然拖垮整个集群。

text=ZqhQzanResources