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

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

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

前言

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

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

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

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