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

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

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

前言

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

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

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

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