Administrator
发布于 2026-01-20 / 1 阅读
0

面鸭突击30天之第九天

MySQL 中如何解决深度分页的问题?

1. 深度分页的定义与痛点

深度分页指 LIMIT 大偏移量, 小条数 形式的查询(如 LIMIT 999999, 10),核心痛点是:

  • MySQL 会先扫描偏移量前的所有数据(如 999999 条),再丢弃这些数据,仅返回目标条数;

  • 大量无效 IO 消耗,偏移量越大,查询效率越低。

2. 核心优化思路

避免「全量扫描 + 全量排序 + 海量跳过」,改为「精准定位起始位置→按需取数」,核心是利用索引降低无效操作成本。

3. 具体解决方案

(1)子查询 / 联表优化(最常用)

适用场景:有唯一有序字段(如自增 ID)、需定位到指定页码的场景。

  • 原始查询(低效):

    SELECT * FROM table WHERE name = "demo" ORDER BY id LIMIT 999999, 10;

    执行逻辑:走 name 二级索引→筛选出所有 name="demo" 的 ID→对 ID 做 filesort(文件排序)→跳过 999999 条 ID→回表取 10 条全字段,核心瓶颈是「百万级 ID 排序」。

  • 优化查询(子查询版):

    SELECT * FROM table 
    WHERE name = "demo" AND id >= (
      SELECT id FROM table WHERE name = "demo" ORDER BY id LIMIT 999999, 1
    ) ORDER BY id LIMIT 10;

    执行逻辑:

    ① 子查询:走 name 二级索引(覆盖索引,仅含 name+ID)→无需排序,直接遍历到第 999999 条取起始 ID(锚点);

    ② 外层查询:用 id>=起始ID 精准筛选→借助主键索引有序性排序(低成本)→仅回表取 10 条数据。

  • 优化查询(联表版)

    SELECT * FROM table 
    INNER JOIN (
      SELECT id FROM table WHERE name = 'demo' ORDER BY id LIMIT 999999, 10
    ) AS t ON table.id = t.id;

    逻辑与子查询一致,仅语法形式不同,需保证 name 有二级索引。

核心优势:保留「定位到指定页码」的能力,性能比原始查询提升 10~100 倍;

注意事项:需依赖唯一有序字段(如 ID),若排序字段为非 ID(如 create_time),需建 (name, create_time) 联合索引,子查询取 create_time 作为锚点。

(2)游标分页

适用场景:仅需连续翻页(如 “下一页”)、无需跳转到指定页码的场景。

  • 实现逻辑:每次查询取上一页的最大 ID 作为条件,避免偏移量:

    -- 第一页
    SELECT * FROM table WHERE name = "demo" ORDER BY id LIMIT 10;
    -- 第二页(假设上一页最大 ID 为 10)
    SELECT * FROM table WHERE name = "demo" AND id > 10 ORDER BY id LIMIT 10;

核心优势:无偏移量,全程走索引,效率无衰减;

缺点

① 仅支持连续翻页,无法直接定位到指定页码;

② 依赖唯一有序字段(如 ID);

③ 删除数据可能导致页面数据错乱(可通过 ID+更新时间戳 做复合游标规避)。

(3)搜索引擎优化

适用场景:海量数据(千万级以上)、无唯一有序字段、需复杂排序 / 筛选的场景。

  • 实现逻辑:将 MySQL 数据同步到 Elasticsearch(ES),利用 ES 的 search_after 特性实现深度分页:

    // ES 示例:用上一页最后一条的 sort 值作为游标
    SearchRequest request = new SearchRequest("table_index");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.termQuery("name", "demo"));
    sourceBuilder.sort("id", SortOrder.ASC);
    sourceBuilder.searchAfter(new Object[]{上一页最大ID}); // 替代偏移量
    sourceBuilder.size(10);
    request.source(sourceBuilder);

核心优势:支持海量数据、复杂排序 / 筛选,无深度分页性能衰减;

注意事项:需保证 MySQL 与 ES 数据同步的一致性(如通过 binlog 同步工具)。

什么是 MySQL 的主从同步机制?它是如何实现的?

1. 核心原理

主从同步的核心是「二进制日志(binlog)的复制与重放」,将主库的事务操作同步到从库,实现数据一致性。

2. 完整同步流程

关键说明

  • binlog:主库记录所有数据修改操作的日志,是同步的数据源;

  • relay log:从库本地存储的主库 binlog 副本,避免直接读取主库 binlog 导致性能损耗;

  • dump线程:主库专门用于给从库推送 binlog 的线程;

  • IO线程:从库负责拉取 binlog 的线程;

  • SQL线程:从库负责解析 relay log 并执行的线程。

3. 同步方式(按主库响应时机划分)

同步方式

核心逻辑

数据可靠性

性能

适用场景

异步复制(默认)

主库写完 binlog 立即返回客户端,不等待从库接收

低(主库宕机可能丢数据)

非核心业务、对数据一致性要求低的场景

同步复制

主库需等待所有从库执行完事务后才返回

最高

极低(几乎不用)

金融级、极致一致性要求的场景

半同步复制

主库等待至少 1 个从库确认接收 binlog 后返回

较高

中(略低于异步)

核心业务、兼顾性能与一致性的场景

半同步复制关键配置(MySQL 5.7+)

-- 开启半同步复制
SET GLOBAL rpl_semi_sync_master_enabled = ON;
SET GLOBAL rpl_semi_sync_slave_enabled = ON;
-- 推荐配置:主库先同步binlog再提交事务(避免主从不一致)
SET GLOBAL rpl_semi_sync_master_wait_point = 'AFTER_SYNC';

4. 并行复制演化(解决从库 SQL 线程单线程瓶颈)

MySQL 版本

并行复制方案

核心逻辑

优势

5.6 及以下

按库并行

不同库的事务可并行执行

仅多库场景有效,单库无效

5.7

组提交并行(LOGICAL_CLOCK)

处于 prepare 阶段的无冲突事务可并行

单库多表场景有效,依赖组提交机制

8.0

Writeset 并行

计算事务修改行的 hash 值(主键 / 唯一索引),不同 hash 的事务可并行

单库单表也可并行,灵活性最高

8.0 并行复制配置

SET GLOBAL slave_parallel_type = 'WRITESET';
SET GLOBAL slave_parallel_workers = 8; -- 一般设为 CPU 核心数的 2 倍

三、MySQL 主从同步延迟问题处理

1. 主从延迟的定义与常见原因

  • 定义:从库数据落后于主库的时间差(可通过 Seconds_Behind_Master 查看);

  • 核心原因:

    ① 硬件:从库配置低于主库、从库查询业务抢占同步资源;

    ② 配置:未开启并行复制、binlog/relay log 配置不合理;

    ③ 业务:主库大事务(如批量插入 / 更新)、从库慢查询;

    ④ 网络:主从跨机房 / 跨地域导致 binlog 传输延迟;

    ⑤ 架构:SQL 线程单线程瓶颈(5.6 前无并行复制)。

2. 延迟处理方案(从易到难)

(1)查询层面优化

  • 关键实时查询走主库:如订单支付、用户登录等核心场景,直接查询主库;

  • 延迟感知路由:记录数据插入时间戳,短时间内(如 5 秒)的查询走主库,超过则走从库;

  • 兜底查询:从库查不到的数据自动降级查主库(如用户最新订单)。

(2)配置层面优化

  • 开启并行复制(5.7+/8.0+):参考上文并行复制配置;

  • 优化 binlog 格式:主库使用 ROW 格式(精准同步),并开启 binlog_row_image = MINIMAL(仅记录修改列,减少日志体积);

  • 调整半同步参数:延长主库等待从库确认的超时时间(避免频繁切回异步):

    SET GLOBAL rpl_semi_sync_master_timeout = 10000; -- 10秒超时

(3)硬件 / 部署层面优化

  • 从库配置不低于主库:CPU / 内存 / 磁盘 IO 需匹配主库,推荐 SSD 磁盘;

  • 拆分从库角色:单独部署「同步从库」(仅做数据同步)和「查询从库」(处理业务查询),避免查询抢占同步资源;

  • 就近部署:主从尽量同机房,跨地域场景可搭建多级从库(主→区域从→本地从)。

(4)架构层面优化

  • 引入中间件:如 MyCat、ShardingSphere,自动路由实时查询到主库、非实时查询到从库;

  • 缓存兜底:写主库时同步更新 Redis 缓存,查询优先读缓存,降低数据库查询压力;

  • 多从库负载均衡:部署多个从库,分摊查询压力,避免单从库过载导致同步延迟。

3. 延迟监控

-- 查看从库延迟(单位:秒)
SHOW SLAVE STATUS\G; -- 关注 Seconds_Behind_Master 字段
-- 查看并行复制状态(8.0+)
SHOW PROCESSLIST; -- 查看 SQL 线程/IO 线程状态
SHOW GLOBAL STATUS LIKE 'Slave_%'; -- 查看并行复制工作线程状态

四、核心知识点总结

知识点

核心记忆点

深度分页优化

1. 子查询 / 联表:覆盖索引找锚点,主键索引兜底排序;2. 游标分页:连续翻页首选;3. ES:海量数据 / 复杂筛选场景

主从同步机制

1. 核心流程:binlog→relay log→SQL 线程重放;2. 同步方式:异步(默认)/ 半同步(推荐 AFTER_SYNC);3. 并行复制:8.0 Writeset 最优

主从延迟处理

1. 优先优化查询路由(实时查主库);2. 开启并行复制 + 半同步;3. 拆分从库角色 + 硬件升级;4. 缓存兜底降低查询压力