• 千万级大表如何做分页查询?

千万级大表如何做分页查询?

2025-08-16 12:32:57 栏目:宝塔面板 41 阅读

前言

在我们的日常开发中,经常会遇到分页查询接口的性能问题。

该接口访问前面几页很快,越往后翻页,接口返回速度越慢。

今天跟大家一起聊聊千万级大表如何高效的做分页查询,希望对你会有所帮助。

1.千万级大表分页为什么性能差?

核心痛点:当千万级别的订单大表需要查询limit 9999990,10时:

SELECT * FROM orders 
ORDER BY create_time DESC 
LIMIT 9999990,10;

在分库分表环境下:

  1. 每个分片需扫描前9999990条
  2. 归并节点需处理分片数 × 1000万数据
  3. 内存溢出风险高达90%

真实案例:某电商订单查询事故

在128分片的订单表上执行深度分页,实际扫描了128 × 1000万 = 12.8亿行数据,导致数据库集群OOM!

2.深分页的常见解决方案

方案1:游标分页(最优解)

原理:基于有序字段的连续分页

public PageResult queryOrders(String lastCursor, int size) {
    if (lastCursor == null) {
        return orderDao.firstPage(size);
    }
    return orderDao.nextPage(lastCursor, size);
}

SQL优化

/* 首次查询 */
SELECT * FROM orders 
ORDERBYidDESC
LIMIT10;

/* 后续查询 */
SELECT * FROM orders 
WHEREid < ?lastId 
ORDERBYidDESC
LIMIT10;

性能对比

分页方式

100万页扫描行数

响应时间

传统limit

128亿行

>30s

游标分页

1280行

10ms

方案2:覆盖索引+延迟关联

适用场景:需要跳页的非连续查询

三步优化法

SQL实现

/* 传统写法(全表扫描) */
SELECT * FROM orders ORDERBY create_time DESCLIMIT9999990,10;

/* 优化写法 */
SELECT * FROM orders 
WHEREidIN (
    SELECTidFROM orders 
    ORDERBY create_time DESC
    LIMIT9999990,10-- 仅扫描索引
);

执行计划对比

类型

扫描行数

是否回表

是否文件排序

传统查询

1000万+

优化查询

10

方案3:全局二级索引

架构设计

Java实现

public List queryByPage(int page, int size) {
    // 1. 查询全局索引
    PositionRange range = indexService.locate(page, size);
    
    // 2. 分片并行查询
    Map>> futures = new HashMap<>();
    for (Shard shard : shards) {
        futures.put(shard.key, executor.submit(() -> 
            shard.query(range.startId, range.endId)
        );
    }
    
    // 3. 结果归并
    List result = new ArrayList<>();
    for (Future> future : futures.values()) {
        result.addAll(future.get());
    }
    return result;
}

方案4:基因分片法

解决分页字段与分片键不一致问题

// 订单ID注入用户基因
long userId = 123456;
long orderId = (userId % 1024) << 54 | snowflake.nextId();

查询优化

SELECT * FROM orders 
WHERE user_id = 123456 
ORDER BY create_time DESC 
LIMIT 9999990,10;

通过user_id路由到同一分片,避免跨分片查询

方案5:冷热分离 + ES同步

架构设计

查询示例

SearchRequest request = new SearchRequest("orders_index");
request.source().sort(SortBuilders.fieldSort("create_time").order(SortOrder.DESC));
request.source().from(9999990).size(10); 
SearchResponse response = client.search(request, RequestOptions.DEFAULT);


ES分页原理:通过search_after实现深度分页"search_after": [lastOrderId, lastCreateTime]

方案6:业务折衷方案

1. 最大页数限制

public PageResult query(int page, int size) {
    if (page > MAX_PAGE) {
        throw new BusinessException("最多查询前" + MAX_PAGE + "页");
    }
    // ...
}

2. 跳页转搜索

3.如何做性能优化?

3.1 索引设计黄金法则

3.2 分页查询检查清单

public void validateQuery(PageQuery query) {
    if (query.getPage() > 1000 && !query.isAdmin()) {
        throw new PermissionException("非管理员禁止深度分页");
    }
    
    if (query.getSize() > 100) {
        query.setSize(100); // 强制限制每页数量
    }
}

3.3 分页监控指标

指标

预警阈值

处理方案

单次扫描行数

>10万

检查是否走索引

分页响应时间

>500ms

优化SQL或增加缓存

归并节点内存使用率

>70%

扩容或调整分页策略

3.4 性能压测对比

方案

100万页耗时

CPU峰值

内存消耗

适用场景

原生limit

超时(>30s)

100%

OOM

禁止使用

游标分页

23ms

15%

50MB

连续分页

覆盖索引

210ms

45%

200MB

非连续跳页

二级索引归并

320ms

60%

300MB

分布式环境

ES搜索

120ms

30%

150MB

复杂查询

基因分片

85ms

25%

100MB

分库分表环境


测试环境:阿里云 PolarDB-X 32核128GB × 8节点

总结

  1. 单体阶段
    limit offset, size + 索引优化
  2. 分库分表初期游标分页 + 最大页数限制
  3. 百万级数据二级索引归并 + 异步构建
  4. 千万级数据ES/Canal准实时搜索
  5. 亿级高并发分布式游标服务 + 状态持久化

分页方案选型表

场景

推荐方案

注意事项

用户连续浏览

游标分页

需有序字段

后台跳页查询

覆盖索引

索引维护成本

分库分表环境

基因分片

分片键设计

复杂条件搜索

ES同步

数据延迟问题

开放平台API

二级索引归并

索引存储空间

历史数据导出

分段扫描

避免事务超时

记住:没有完美的方案,只有最适合业务场景的权衡。

没有最好的方案,只有最适合场景的设计。

本文地址:https://www.yitenyun.com/330.html

搜索文章

Tags

数据库 API FastAPI Calcite 电商系统 MySQL Web 应用 异步数据库 数据同步 ACK 双主架构 循环复制 TIME_WAIT 运维 负载均衡 JumpServer SSL 堡垒机 跳板机 HTTPS JumpServer安装 堡垒机安装 Linux安装JumpServer Deepseek 宝塔面板 Linux宝塔 Docker 生命周期 esxi esxi6 root密码不对 无法登录 web无法登录 HexHub 序列 核心机制 Windows Windows server net3.5 .NET 安装出错 服务器 管理口 HTTPS加密 宝塔面板打不开 宝塔面板无法访问 查看硬件 Linux查看硬件 Linux查看CPU Linux查看内存 Windows宝塔 Mysql重置密码 Oracle 处理机制 InnoDB 数据库锁 无法访问宝塔面板 开源 PostgreSQL 存储引擎 监控 连接控制 机制 Serverless 无服务器 语言 ES 协同 技术 Spring Redis 异步化 服务器性能 分页查询 索引 缓存方案 缓存架构 缓存穿透 group by 高可用 Undo Log GreatSQL 连接数 SQL 动态查询 机器学习 日志文件 MIXED 3 响应模型 R2DBC SVM Embedding 优化 万能公式 R edis 线程 数据 主库 自定义序列化 RocketMQ 长轮询 配置 Postgres OTel Iceberg 工具 云原生 Netstat Linux 服务器 端口 Linux 安全 ​Redis 推荐模型 SQLark scp Linux的scp怎么用 scp上传 scp下载 scp命令 AI 助手 共享锁 PG DBA 向量数据库 大模型 openHalo 存储 SQLite-Web SQLite 数据库管理工具 Hash 字段 Recursive 电商 系统 OB 单机版 查询 Ftp 架构 • 索引 • 数据库 流量 Rsync 锁机制 修改DNS Centos7如何修改DNS redo log 重做日志 数据分类 加密 磁盘架构 聚簇 非聚簇 sftp 服务器 参数 向量库 Milvus 线上 库存 预扣 人工智能 推荐系统 场景 MySQL 9.3 防火墙 黑客 业务 同城 双活 信息化 智能运维 Python 高效统计 今天这篇文章就跟大家 Doris SeaTunnel MVCC 传统数据库 向量化 不宕机 数据备份 mini-redis INCR指令 缓存 Redisson 锁芯 RDB AOF INSERT COMPACT 网络架构 网络配置 分库 分表 窗口 函数 prometheus Alert PostGIS 启动故障 事务 Java 开发 Canal Web 崖山 新版本 filelock MongoDB 数据结构 IT运维 B+Tree ID 字段 分布式 集中式 ZODB 核心架构 订阅机制 引擎 性能 数据脱敏 加密算法 Go 数据库迁移 数据类型 虚拟服务器 虚拟机 内存 读写 容器 DBMS 管理系统 频繁 Codis 模型 Redis 8.0 网络故障 容器化 OAuth2 Token JOIN QPS 高并发 微软 SQL Server AI功能 Pottery 聚簇索引 非聚簇索引 原子性 发件箱模式 自动重启 部署 Entity 工具链 国产数据库 SpringAI Testcloud 云端自动化 速度 服务器中毒 事务隔离 分页方案 排版 排行榜 排序 SSH Caffeine CP Web 接口 行业 趋势 数据页 数据集成工具 MCP 开放协议 悲观锁 乐观锁 StarRocks 数据仓库 Redka sqlmock LRU 大表 业务场景 分布式架构 分布式锁​ 1 分页 AIOPS dbt 数据转换工具 优化器 池化技术 连接池 单点故障 仪表盘 网络 Order 意向锁 记录锁 EasyExcel MySQL8 InfluxDB 事务同步 日志 IT 字典 RAG HelixDB 对象 双引擎 播客 订单 单线程 Crash 代码 主从复制 代理 编程 UUIDv7 主键 Ansible LLM UUID ID 语句 Valkey Valkey8.0 恢复数据 Pump ReadView 线程安全 数据字典 兼容性 产业链 List 类型 Weaviate 失效 MGR 分布式集群 表空间 解锁 调优 Next-Key 分布式锁 Zookeeper 关系数据库 慢SQL优化 GitHub Git 查询规划 矢量存储 数据库类型 AI代理 国产 用户 RR 互联网 算法 千万级 快照读 当前读 视图 神经系统 技巧 count(*) count(主键) 行数 CAS 拦截器 动态代理 多线程 并发控制 恢复机制 闪回