优秀的编程知识分享平台

网站首页 > 技术文章 正文

为什么我们禁止了MySQL深分页查询?原理分析与实战优化

nanyue 2025-03-19 00:43:40 技术文章 8 ℃

大家好,我是对线面试官。昨晚被一个P0故障电话惊醒:

  • 商品列表页超时
  • CPU 100%告警
  • 数据库连接打满
  • 运维差点被炸醒!

一、深分页为什么这么慢?

1. 执行原理分析

SELECT * FROM products 
WHERE status = 1
ORDER BY create_time DESC 
LIMIT 100000, 20;

MySQL执行步骤: 1 从头开始扫描表 2 过滤status=1的记录 3 根据create_time排序 4 丢弃前100000条 5 返回20条记录

问题在哪? 必须先扫描10W条记录 可能产生临时表排序 大量无用的回表操作 内存占用可能爆炸

2. 执行计划分析



EXPLAIN SELECT * FROM products 
WHERE status = 1
ORDER BY create_time DESC 
LIMIT 100000, 20;

性能杀手:

type=ALL:全表扫描

Using filesort:需要排序

rows=100020:扫描行数巨大



二、优化方案原理解析

1. 延迟关联方案



SELECT p.* 
FROM products p
JOIN (
    SELECT id 
    FROM products 
    WHERE status = 1
    ORDER BY create_time DESC 
    LIMIT 100000, 20
) tmp ON p.id = tmp.id;

优化原理:

1 子查询只查id,减少数据量

2 利用覆盖索引避免回表

3 JOIN时只关联20条记录

4 显著减少内存占用

执行计划对比:

EXPLAIN SELECT p.* 
FROM products p
JOIN (
    SELECT id 
    FROM products FORCE INDEX(idx_create_time)
    WHERE status = 1
    ORDER BY create_time DESC 
    LIMIT 100000, 20
) tmp ON p.id = tmp.id;



2. 游标分页方案

public PageResult queryByCursor(Long lastId, int size) {
    return productMapper.selectList(
        new QueryWrapper()
            .lt("id", lastId)
            .orderByDesc("id")
            .last("LIMIT " + size)
    );
}

优化原理:

1 利用自增ID的单调性

2 每次查询都是范围查询

3 不需要计算偏移量

4 性能稳定不衰减

索引使用分析:

产品表索引结构:
PRIMARY KEY (id)
│
├── 1
│   └── data
├── 2
│   └── data
└── 3
    └── data

游标查询时:
WHERE id < last_id
直接在B+树中定位,效率稳定



3. ES实时同步方案



@Component
public class ProductSyncService {
    
    // ES分页原理
    public SearchResponse search(SearchRequest request) {
        // 1. ES内部使用from+size
        // 2. 分片内部排序
        // 3. 协调节点合并
        return esClient.search(s -> s
            .index("products")
            .from(request.getFrom())  // 偏移量
            .size(request.getSize()), // 页大小
            Product.class
        );
    }
}

ES优化原理:

1 倒排索引加速查询

2 分片内并行处理

3 文档预排序

4 内存映射技术



三、性能对比与原理分析

方案

原理

优点

缺点

适用场景

原始分页

偏移量+限制数

实现简单

性能随页数下降

浅分页

延迟关联

子查询+JOIN

减少回表

需要合适索引

中等分页

游标分页

范围查询

性能稳定

不支持跳页

深分页

ES分页

分布式查询

性能最好

维护成本高

搜索场景



四、内存分析

1. 原始分页内存问题

问题点:
1. 临时表排序
   - sort_buffer_size限制
   - 可能触发磁盘排序

2. 扫描记录缓存
   - innodb_buffer_pool占用
   - 可能驱逐热数据

2. 优化方案内存分析

// 游标方案内存优势
public class CursorPageHandler {
    
    @Transactional(readOnly = true)
    public PageResult getPage(Long cursor) {
        // 1. 固定LIMIT大小
        // 2. 不需要临时表
        // 3. 索引扫描量稳定
        return productMapper.selectList(
            new QueryWrapper()
                .lt("id", cursor)
                .orderByDesc("id")
                .last("LIMIT " + size)
        );
    }
}



五、监控与预警

1. 慢查询监控

@Aspect
@Component
public class PageQueryMonitor {
    
    @Around("@annotation(PageQuery)")
    public Object monitorPageQuery(ProceedingJoinPoint pjp) {
        // 监控指标:
        // 1. 查询耗时
        // 2. 扫描行数
        // 3. 临时表大小
        // 4. 内存使用
    }
}

2. 性能基线

查询延迟 < 100ms

扫描行数 < 1000

临时表使用率 < 1%

内存使用率 < 80%


写在最后

深分页优化需要:

  • 理解数据库执行原理
  • 掌握索引使用规则
  • 权衡业务实际需求
  • 建立完善监控体系


#MySQL #性能优化 #分页查询 #实战经验

Tags:

最近发表
标签列表