首页 > 项目 > 当前页面

分库分表怎么分页?维护一个全局主键表可行吗?

2026-06-13 NEW个对象

📌 分库分表怎么分页?维护一个全局主键表可行吗?

核心结论:
分库分表后,传统的 MySQL 分页方式会失效,因为数据已经分散到多个库和多个表中。实际项目中通常采用:
  • 基于全局唯一ID分页(推荐)
  • ES搜索分页
  • 中间件聚合分页(ShardingSphere)
  • 业务维度分页
维护一个全局主键表虽然可以生成唯一ID,但通常不会用于分页,而是用于生成全局唯一主键。

1️⃣ 问题背景

当系统数据量达到千万级甚至亿级以后,单表查询性能开始下降。

例如订单系统:

订单表:order_info
数据量:10亿条

此时通常会进行分库分表:

order_0
order_1
order_2
...
order_63

甚至:

db0.order_0~15
db1.order_16~31
db2.order_32~47
db3.order_48~63

此时出现一个非常经典的问题:

用户查看订单列表时:

SELECT * FROM order LIMIT 0,20;

已经无法执行。

2️⃣ 核心原理

为什么传统分页失效?

在单表时代:

SELECT *
FROM order_info
ORDER BY id DESC
LIMIT 0,20;

数据库只扫描一张表即可。

而分库分表后:

db0.order_0
db0.order_1
...
db3.order_63

数据分布在64张表中。

如果还想获取前20条数据:

  • 每张表先查20条
  • 64张表共返回1280条
  • 应用层排序
  • 取最终20条

这就是所谓的:

跨库分页问题

3️⃣ 数据结构分析

订单表结构

CREATE TABLE order_info(
  id BIGINT PRIMARY KEY,
  user_id BIGINT,
  amount DECIMAL(10,2),
  create_time DATETIME
);

按照订单ID进行分片:

order_${id % 64}

例如:

  • 1001 → order_41
  • 1002 → order_42
  • 1003 → order_43

数据被打散存储。

4️⃣ 算法分析

方案一:全量聚合分页

最简单但性能最差。

查询64张表

结果汇总

排序

分页

时间复杂度:

O(NlogN)

数据量大时无法接受。

方案二:基于ID游标分页(推荐)

解决了深分页问题,依旧需要聚合。

互联网公司最常见方案。

第一次查询:

SELECT *
FROM order_info
WHERE id <= MAX_ID
ORDER BY id DESC
LIMIT 20;

返回最后一条记录:

lastId = 9856321

下一页:

SELECT *
FROM order_info
WHERE id < 9856321
ORDER BY id DESC
LIMIT 20;

这种方式不需要offset。

✅ 电商订单系统几乎都采用游标分页。

方案三:ES分页

如果需要复杂查询:

  • 订单搜索
  • 商品搜索
  • 日志查询

通常同步数据到ES。

分页由Elasticsearch完成。

淘宝、京东、美团等大型系统大量使用这种方案。

5️⃣ 执行流程

基于全局ID分页流程

用户请求第一页

查询各分片前20条数据

应用层归并排序

返回20条结果

记录最后一个ID(lastId)

用户请求下一页

WHERE id < lastId

继续查询

全局ID生成流程

订单创建

获取全局唯一ID

根据ID计算分片

写入对应库表

6️⃣ 实际案例

方案一:全局主键表

很多同学首先想到:

CREATE TABLE global_id(
  id BIGINT AUTO_INCREMENT PRIMARY KEY
);

获取主键:

INSERT INTO global_id VALUES(NULL);
SELECT LAST_INSERT_ID();

这样确实可以生成唯一ID。

但是有明显问题:

  • 单点故障
  • 数据库压力集中
  • 高并发下性能差
  • 扩展性不足
❌ 互联网大厂基本不会采用全局主键表方案。

方案二:Snowflake雪花算法

目前最主流方案。

0 | 时间戳 | 数据中心ID | 机器ID | 序列号

生成64位Long类型ID。

特点:

  • 全局唯一
  • 趋势递增
  • 无需数据库
  • 支持分布式部署

例如:

1917281638127387238

根据ID直接分片:

tableIndex = id % 64;

7️⃣ 优缺点分析

方案 优点 缺点
全局主键表 简单 单点瓶颈
UUID 全局唯一 索引离散
Redis自增 性能高 依赖Redis
Snowflake 高性能、分布式 时钟回拨问题

8️⃣ 面试常见问题

Q1:分库分表后还能使用 limit 分页吗?

可以,但需要所有分片查询后再聚合排序,性能较差。

Q2:为什么推荐游标分页?

避免深度分页导致大量数据扫描,性能稳定。

Q3:维护一个全局主键表可以吗?

理论上可以,但并发量大时会成为瓶颈,因此生产环境很少使用。

Q4:互联网公司一般怎么生成主键?

Snowflake雪花算法、Leaf、美团Leaf、百度UidGenerator等。

Q5:订单列表分页怎么做?

优先按照用户维度分片,然后采用ID游标分页。

Q5:如何记录lastId

分页返回的数据如下,之后分页携带nextCursor即可。

{

  "list":[

    {"id":100},

    {"id":99},

    {"id":98}

  ],

  "nextCursor":98

}

9️⃣ 总结

📌 分库分表最大的难点不是分片,而是分页、排序和统计。

📌 全局主键表可以生成唯一ID,但无法解决分页问题,并且存在单点瓶颈。

📌 当前主流方案是 Snowflake + 分片键路由 + 游标分页。

📌 深度分页场景优先考虑 ES 搜索分页或基于ID的游标分页,而不是传统 limit offset 分页。

📌 面试中如果被问到“分库分表怎么分页”,最佳回答通常是:ShardingSphere聚合分页 + 游标分页 + Snowflake全局ID。

相关文章

NEW个对象 NEW个对象
JAVA是世界上最好的语言