带与不带,是两种人生
分片系统里同一张逻辑表的两个查询,命运天差地别。where user_id=9527 走精确路由,一个库一张表,单表索引伺候,快得像从没分过片;where status=0 order by create_time desc 不带分片键,被广播到 8 库 32 表,每个分片各自执行、各自排序,再由中间件把 32 份结果归并成一份。聚合与排序,是归并开销最重的两类查询——这篇把它们的账本逐项算清。
count 与 sum:最老实的归并
count(*) 与 sum(amount) 的归并语义是可分解的:全局计数等于各分片计数之和,全局求和等于各分片求和之和。中间件把逻辑 SQL 广播下去,每个分片返回一个数字,加法归并完事。正确性无忧,但代价仍在:32 个分片都要完整扫描自己的数据(或索引),整体耗时取决于最慢的分片。count 类查询的优化老三样在分片下依旧成立——按条件建索引、避免无谓的全表 count,外加一条分片专属的:精确的全局 count 高频使用时,别实时算,用计数器或统计表异步维护。
avg:归并里的头号陷阱
avg 是唯一「数学上不可直接分解」的常用聚合:全局平均值不等于各分片平均值的平均。分片 A 一亿行均值 100,分片 B 十行均值 1000,直接对两个 avg 求平均得 550,而真实的全局均值约等于 100——差出五倍。正确做法是改写阶段就把 avg 拆成 sum 与 count,归并时拿全局 sum 除以全局 count(第 8 篇的改写日志里那两个 AVG_DERIVED 就是干这个的)。成熟中间件会自动处理,要警惕的是自己手写的「分片循环加求平均」——那是真实的线上 bug 温床。
ORDER BY:全局有序怎么拼
广播查询里带 ORDER BY,中间件不会把 32 份结果拉回内存重新排——那样内存与耗时都爆炸。它的做法是多路归并:每个分片返回的本来就是各自有序的结果流(单表有索引时甚至有序读盘),归并器同时盯着 32 个流的队头,每次挑出全局最小的输出,流式推进直到凑够结果。内存友好,但有一个隐性代价:32 个连接、32 个游标要同时打开并维持到归并结束——连接池瞬间被占满的「连接风暴」,很多就是跨片排序查询堆出来的。带分片键的查询没有这个问题,这再次把铁律钉牢。
GROUP BY 与工程取舍
GROUP BY 是最重的一档:先按分组键把各分片的行重新组织,再在归并层聚合——分组基数越大,归并的内存与耗时越失控。分片数据库不适合做实时 OLAP,大范围 group by、多维统计、报表,请走另一条路:T+1 离线汇总到数仓,或把数据同步进专门的检索/分析引擎(ES、ClickHouse 这类)。工程上的取舍口诀:在线查询尽量带分片键;跨片聚合能异步就异步,能预计算就预计算;实在要实时的,让专门的引擎去扛。
聚合与排序讲完,归并账本还剩最贵的一页——分页与 JOIN。翻到第 100 页为什么慢出天际?三张表怎么 JOIN 才不散架?下一篇收掉跨分片查询的下半场。
评论 (0)