越翻越贵的分页
单表深分页的病并不新鲜:limit 100000,10 要扫过前十万行。分片环境下这个病被归并机制放大了数倍——取第 100 页的 10 条,中间件要向每个分片各要回前 1010 条(改写逻辑第 8 篇讲过),32 个分片送回 32320 行,内存里排完序丢掉前 1000 条。翻到第 1000 页,这个数字变成 320 万行。深分页在分片下不是线性变慢,是分片数乘以翻页深度的乘法变慢。
解法与单表深分页一脉相承,再加一条分片专属的。游标分页(scroll):不用 limit 跳页,用「上一页最后一条的排序值」作游标——where user_id=9527 and create_time < ? order by create_time desc limit 10,每页都是带条件的限量查询,翻多深都一样的代价;二次查询法:第一次查各分片的第 100 页最小排序值,取全局最小,第二次从该值精确捞 10 条——两次往返换掉大结果集传输,适合必须支持跳页的场景;产品降级:后台列表限制只翻 500 页,跳页需求引导到搜索或导出——这是最省钱的一招,别不好意思用。
JOIN 的三种命运
JOIN 在分片下的命运由两张表的位置关系决定,恰好三种。第一种:同片 JOIN,一切照旧——订单表和订单明细表都用 order_id 的基因(或同一分片键)路由,同一笔订单的行永远落在同一个库里,JOIN 被下推到库内正常执行,性能与单表时代无异。这就是绑定表的用法:把经常一起查的表用同一个分片键绑定,让它们天然同片居住。
第二种:广播表,小表特权——商品表、字典表、配置表这类「小而全」的维表,在每个分片库里都放一份完整的(广播),JOIN 退化成库内操作。代价是写入要同步到所有分片——广播表必须小、少、变更慢,几十万行的维表广播 32 份还能接受,千万行的业务表广播就是灾难。第三种:跨片 JOIN,原则上禁止——两张大表分片键不同、数据不同片,中间件要么把 A 表结果拉回来内存里和 B 表拼(内存爆炸),要么直接不支持。跨库 JOIN 从垂直分库那篇起就是消失的能力,这里只是再次确认:跨片 JOIN 要用数据冗余、异构索引或应用层聚合来替代。
设计期的预防针
跨片查询的三难——聚合、分页、JOIN——解法各不相同,但预防思路完全一致,都在拆分设计期完成:高频关联的表用绑定表锁进同片;维表规划成广播表;跨维度的查询需求(卖家视角、运营报表)提前规划异构方案而不是指望 JOIN;分页场景设计期就约定游标分页。拆分之后再补救,每一条都要付出数据迁移级别的代价——分库分表的功夫,一半在拆之前。
查询篇到此收官。接下来是分片系统更深的水:事务。一个业务动作要写多个分片时,本地事务的边界在哪、柔性方案怎么选——下一篇进入这个系列下半场。
评论 (0)