分表后count(*)和sum()不准,因mysql不支持跨分表原子性统计,单表扫描漏数据、union去重破坏数量,需用union all+外层sum;group by应先分表局部聚合再汇总;distinct类聚合须应用层归并或估算;查询须匹配分表键避免全表扫描;应用层聚合比数据库层更可控可靠。

分表后 COUNT(*) 和 SUM() 为什么不准?
因为跨分表聚合时,MySQL 默认不支持分布式事务下的原子性统计,COUNT(*) 或 SUM() 直接扫单表会漏数据,用 UNION ALL 拼结果又容易因排序/去重逻辑出错。
- 常见错误现象:对
order_202501、order_202502分别COUNT(*)后加总,但实际业务中某条记录可能被误写入两个表(比如时间字段解析错误),导致重复计数 - 真正可靠的做法是用
SELECT SUM(cnt) FROM (SELECT COUNT(*) AS cnt FROM order_202501 UNION ALL SELECT COUNT(*) FROM order_202502) t—— 注意必须用UNION ALL,UNION会去重,反而破坏数量准确性 - 如果分表数超过 20 张,这种手写
UNION ALL易出错且难维护,建议用脚本动态生成 SQL,或引入中间层(如 MyCat、ShardingSphere)接管聚合逻辑
GROUP BY 跨分表聚合怎么避免内存溢出?
当按用户 ID 或日期做 GROUP BY 并聚合时,各分表返回的中间结果若未提前裁剪,合并时可能触发 MySQL 的 sort_buffer_size 不足,报错 Lost connection to MySQL server during query 或 Out of memory。
- 关键做法:在每个分表子查询里先完成局部聚合,再汇总。例如统计每日订单金额,不要
SELECT user_id, amount FROM order_202501 UNION ALL ... GROUP BY user_id,而要SELECT user_id, SUM(amount) AS amt FROM order_202501 GROUP BY user_id,每个表只返回几十或几百行,再用外层UNION ALL+GROUP BY - 注意
GROUP_CONCAT()类函数不能直接跨表拼接,它受group_concat_max_len限制,且各分表结果独立截断,最终拼出来可能丢字段 - 如果聚合字段含
DISTINCT,比如COUNT(DISTINCT user_id),纯 SQL 几乎无法精确实现,必须靠应用层归并去重,或改用 HyperLogLog 估算
时间范围查询时如何避免全表扫描所有分表?
分表键如果是 create_time,但查询条件用的是 update_time,MySQL 无法自动路由到目标分表,会扫全部物理表,IO 和 CPU 压力陡增。
- 必须确保查询 WHERE 条件中至少有一个等值匹配分表键(如
WHERE create_time BETWEEN '2025-01-01' AND '2025-01-31'),否则优化器不会跳过无关分表 - 如果业务确实需要按
update_time查询,有两种务实解法:一是冗余分表键(把update_time也作为分表依据之一,比如按月分表+按更新周哈希二级分片);二是建覆盖索引 +FORCE INDEX,但效果有限 - 用
EXPLAIN验证执行计划:看到type: ALL且rows累加远超预期,基本说明没走分表剪枝
应用层聚合和数据库层聚合哪个更稳?
数据库层聚合看似“省事”,但一旦分表逻辑变更(比如新增分表、调整路由规则),SQL 很容易漏掉新表或重复查旧表;应用层虽然多写几行代码,却能把分表路由、失败重试、结果校验都收口控制。
- 推荐模式:应用层遍历分表列表(从配置中心或元数据表读取),对每张表发独立
SELECT SUM(...) WHERE ...,汇总后再校验总和是否与分表数匹配(比如 12 张月表,但只收到 11 个响应,立刻告警) - 避免用存储过程做跨表聚合 —— MySQL 存储过程不支持动态表名拼接执行(
PREPARE+EXECUTE在存储过程中受限),且难以调试和监控 - 如果用了 ShardingSphere 这类中间件,务必确认其
sql.show=true日志是否开启,否则你根本不知道它底层到底发了几条 SQL、有没有漏表











