挑战分库分表后实现跨分页查询

想象你有一个图书馆(单库单表),所有书按顺序放在书架上。当你要找第100-110本书时,直接数到第100本就能拿到。但图书馆的书爆炸式增长后,馆长决定:

分库:把书分到10个房间(10个数据库)

分表:每个房间再分成20个书架(20张表)
每个书架只放特定规则的书(比如按ID取模:ID % 200)

问题来了:
当用户要求「按时间倒序排列,显示第1000-1010条数据」时:

每个房间的书架都是独立排序的

无法直接知道全局第1000条数据在哪里

解决方案:

想象你管理10个快递分拣站(分库分表),每个站点有自己的包裹编号。现在要找出全国第1000-1010个发出的包裹:

笨方法:让所有站点把前1010个包裹发到总部,总部排序后挑出需要的(全局排序法)

聪明方法:

先问每个站点第1010个包裹的发货时间

找到全国第1010个包裹的时间

让各站点只发送比这个时间晚的包裹,再次排序(二次查询法)

最懒但有效的方法:把所有包裹信息抄一份到总部的智能黑板(Elasticsearch),随时查黑板即可!

下面具体说下各个方案、优缺点和做法:

方案 适用场景 技术实现难度 缺点
全局排序法 数据量小,分片少 低 性能差,深分页爆炸
二次查询法 排序字段有索引,分片较多 中 需要两次查询
游标分页法 有全局有序字段(如ID、时间戳) 低 不支持随机跳页
搜索引擎辅助 高频复杂查询,容忍秒级延迟 高
维护成本高,数据延迟

(1)全局排序法(简单但低效)
实现步骤:

每个分片独立执行ORDER BY create_time DESC LIMIT 0, 1010

把所有分片的结果汇总到一个中心节点

中心节点对所有数据进行全局排序,再取第1000-1010条

缺点:

分片数越多,数据传输量指数级增长

深分页(offset很大)时性能灾难(例如:offset=100万)

(2)二次查询法(优化性能)
核心思路:避免全量数据传输,分两次查询缩小范围

步骤示例(查询第1000-1010条):

第一次查询:每个分片返回前1010条数据的最小时间戳

例如分片1的最小时间戳是T1,分片2是T2...

取所有分片中第1010小的全局时间戳T_global

第二次查询:各分片查询create_time >= T_global的数据

汇总后再次排序取最终结果

优点:

大幅减少数据传输量

适合排序字段有索引的情况(如时间戳)

(3)游标分页法(避免深分页)
核心思路:用连续且唯一的字段(如自增ID、时间戳)作为游标

优势:

完全避免offset带来的性能问题

天然适合分库分表,每个分片只需按游标条件过滤

限制:

必须有一个全局有序且唯一的字段

用户无法直接跳转到任意页码(如第1000页)

(4)搜索引擎辅助(终极武器)

适用场景:高频复杂分页查询
实现方式:

将分库分表的数据实时同步到Elasticsearch/Solr

所有分页查询直接走搜索引擎

搜索引擎内部维护全局排序

优势:

完全屏蔽分库分表复杂性

支持复杂条件过滤+高并发查询

代价:

需要维护额外系统,数据一致性延迟增加

实际开发中的建议
优先使用游标分页:

如App的瀑布流页面,用户只会不断下滑,不需要跳页

限制最大分页深度:

例如最多允许查看前100页,防止恶意深分页攻击

结合业务优化:

电商按价格排序时,可预先对价格分段(0-100元、100-200元...)

终极方案:

分库分表+Elasticsearch组合,牺牲一定实时性换取高性能查询

你可能感兴趣的:(java)