0

0

如何设计和优化MySQL中的大表分页查询方案

betcha

betcha

发布时间:2025-09-11 14:26:01

|

532人浏览过

|

来源于php中文网

原创

如何设计和优化mysql中的大表分页查询方案

设计和优化MySQL中的大表分页查询,核心在于规避传统

LIMIT OFFSET
在大偏移量下的性能瓶颈,转而采用更高效的基于索引的查询策略,也就是我们常说的键集(Keyset)分页,辅以合理的索引设计和查询优化,确保数据获取的效率与准确性。这不仅仅是技术选择,更是对用户体验和系统资源消耗的深思熟虑。

解决方案

处理MySQL大表分页查询,我们首先要认识到

LIMIT offset, limit
模式的局限性。当
offset
值非常大时,数据库需要扫描
offset + limit
行,然后丢弃前面的
offset
行,这在数据量巨大时会造成显著的性能下降。因此,解决方案的核心是避免这种全量扫描,转而利用索引的有序性,从上一次查询的“末尾”继续向后查找。

最推荐的方案是键集(Keyset)分页,也被称为“游标分页”或“基于主键/唯一键分页”。这种方法不是通过跳过行数来定位,而是通过记录上一页最后一条记录的某个唯一标识(如主键ID或排序字段的组合),作为下一页查询的起始点。

例如,如果你的表有一个自增主键

id
,并且你希望按
id
升序分页: 第一页:
SELECT * FROM your_table ORDER BY id ASC LIMIT 10;
假设第一页最后一条记录的
id
是100。 第二页:
SELECT * FROM your_table WHERE id > 100 ORDER BY id ASC LIMIT 10;
以此类推。

如果你的排序字段不是唯一的,或者需要多字段排序,例如按

create_time
降序,再按
id
降序: 第一页:
SELECT * FROM your_table ORDER BY create_time DESC, id DESC LIMIT 10;
假设第一页最后一条记录的
create_time
是'2023-10-26 10:00:00',
id
是500。 第二页:
SELECT * FROM your_table WHERE (create_time < '2023-10-26 10:00:00') OR (create_time = '2023-10-26 10:00:00' AND id < 500) ORDER BY create_time DESC, id DESC LIMIT 10;
这种复合条件查询利用了MySQL的行比较(tuple comparison)特性,能够高效地定位下一页的起始位置。

这种方案的关键在于,

WHERE
子句能够直接利用索引进行快速定位,避免了对大量不必要行的扫描和排序,性能表现与
offset
大小无关,始终保持高效。

LIMIT OFFSET
在大表查询中为什么效率低下?

这个问题其实挺直观的,但很多人在实际开发中容易忽视。当我们在MySQL里写

SELECT * FROM some_table ORDER BY some_column LIMIT 100000, 10;
这样的语句时,数据库引擎并不是魔术般地直接跳到第100000条记录。它实际上需要做的是:首先,根据
ORDER BY
子句对所有符合条件的行进行排序(如果数据量大,这本身就是个耗时操作,可能涉及文件排序)。接着,它会从排序后的结果集中,从第一行开始,逐行扫描,直到它跳过了100000行。只有在跳过这些行之后,它才会开始收集接下来的10行数据。

这就像你让一个朋友去图书馆找一本在第10万页的某个单词。他不能直接翻到第10万页,他得一页一页地翻过去,直到找到那一页。在这个过程中,前面99999页的内容他都看了,但都是为了“跳过”而看。对于数据库来说,这意味着大量的磁盘I/O和CPU计算资源被消耗在那些最终会被丢弃的数据上。特别是当

offset
值变得巨大时,这种浪费就非常惊人,查询响应时间会急剧增加,甚至可能导致数据库负载过高。在我看来,这简直就是一种资源浪费的典范,尤其在互联网应用中,用户可不会等那么久。

如何基于键集(Keyset)实现高效分页?

键集分页,或者说游标分页,是我个人在处理大表分页时最喜欢用的方案,因为它真的能带来质的飞跃。它的核心思想是“我不需要知道我是第几页,我只需要知道上一页的最后一项是什么,然后从那里开始找下一页”。这和我们日常阅读一本书,记住上次读到哪里,下次从那儿接着读,是异曲同工的。

具体实现上,我们不再使用

offset
。取而代之的是,我们利用一个或一组唯一标识符(键)来标记当前页的结束位置,作为下一页查询的起始点。

1. 基于单一主键(通常是自增ID)的键集分页: 这是最简单也最常见的场景。假设你的表

products
有一个自增主键
id

  • 首次查询(第一页):
    SELECT id, name, price FROM products ORDER BY id ASC LIMIT 20;

    假设查询结果的最后一条记录的

    id
    last_id_on_page_1

    MvMmall 网店系统
    MvMmall 网店系统

    免费的开源程序长期以来,为中国的网上交易提供免费开源的网上商店系统一直是我们的初衷和努力奋斗的目标,希望大家一起把MvMmall网上商店系统的免费开源进行到底。2高效的执行效率由资深的开发团队设计,从系统架构,数据库优化,配以通过W3C验证的面页模板,全面提升页面显示速度和提高程序负载能力。3灵活的模板系统MvMmall网店系统程序代码与网页界面分离,灵活的模板方案,完全自定义模板,官方提供免费模

    下载
  • 查询下一页:
    SELECT id, name, price FROM products WHERE id > last_id_on_page_1 ORDER BY id ASC LIMIT 20;

    这种方式利用了

    id
    上的B+树索引,MySQL可以直接定位到
    last_id_on_page_1
    之后的第一个记录,然后顺序读取20条,效率极高。

2. 基于复合键(多列)的键集分页: 当你的排序条件不止一个,或者排序字段本身不唯一时,就需要用到复合键。例如,按

created_at
降序,然后按
id
降序。

  • 首次查询(第一页):
    SELECT id, name, created_at FROM orders ORDER BY created_at DESC, id DESC LIMIT 20;

    假设查询结果的最后一条记录是

    (last_created_at_on_page_1, last_id_on_page_1)

  • 查询下一页:
    SELECT id, name, created_at
    FROM orders
    WHERE (created_at < 'last_created_at_on_page_1') OR
          (created_at = 'last_created_at_on_page_1' AND id < last_id_on_page_1)
    ORDER BY created_at DESC, id DESC
    LIMIT 20;

    这里利用了MySQL的行比较特性。

    WHERE
    子句的逻辑是:找到
    created_at
    比上一页最后一条记录的
    created_at
    更小的记录(因为是降序),或者
    created_at
    相同但
    id
    更小的记录。这要求
    created_at
    id
    上有一个复合索引,例如
    INDEX (created_at, id)
    ,这样查询才能高效地利用索引。

优点: 性能稳定,无论翻到多远的页,查询效率几乎不变。它避免了扫描大量无用数据。 缺点: 无法直接跳到任意页(比如“第500页”)。用户只能“下一页”、“上一页”地浏览。这通常需要在产品设计上进行权衡,很多时候用户并不真的需要跳到任意页。

除了键集分页,还有哪些优化策略和注意事项?

当然,键集分页虽好,但它也不是万能药,尤其是在一些特定的业务场景下,比如用户真的需要快速跳转到某个特定页码。即便如此,我们还有其他一些辅助策略和需要注意的地方,来共同构建一个健壮的分页方案。

1. 索引的精确设计: 这是任何查询优化的基石。确保你的

ORDER BY
WHERE
子句中使用的字段都有合适的索引。

  • 覆盖索引 (Covering Index): 如果你的
    SELECT
    列表只包含索引中的字段,那么MySQL可以直接从索引中获取所有需要的数据,而无需回表查询实际数据行。这能极大提升性能。
    -- 假设你经常查询id, name, created_at,并且分页通常是按created_at排序
    CREATE INDEX idx_created_at_id_name ON orders (created_at, id, name);
    -- 如果name字段不需要排序,但经常被查询,可以作为索引的额外列

    如果只查询

    id
    created_at
    ,那么
    INDEX (created_at, id)
    就是一个覆盖索引。

  • 复合索引的顺序: 复合索引的列顺序很重要。通常,将最常用于
    WHERE
    子句过滤的列放在前面,然后是
    ORDER BY
    子句中的列。

*2. 避免 `SELECT `:** 这是一个老生常谈但极其重要的优化点。只查询你真正需要的列。减少数据传输量可以显著降低网络I/O和数据库的内存消耗。尤其是在大表分页中,每一行的数据量累积起来都可能很可观。

3. 对

LIMIT OFFSET
的“曲线救国”式优化: 如果业务场景确实无法放弃任意页码跳转,并且
LIMIT OFFSET
性能瓶颈明显,可以考虑一种结合了子查询的优化方法。它的思路是先通过主键或索引快速定位到需要的
id
范围,然后再回表查询完整数据。

SELECT t.*
FROM your_table AS t
JOIN (
    SELECT id FROM your_table ORDER BY id ASC LIMIT 100000, 10
) AS sub ON t.id = sub.id;

这种方式的原理是,内层的子查询

SELECT id FROM your_table ORDER BY id ASC LIMIT 100000, 10
只查询
id
,如果
id
上有索引,这个查询会相对较快,因为它只涉及索引扫描,不回表。外层再通过
JOIN
将这些
id
对应的完整数据取出来。虽然比纯粹的
LIMIT OFFSET
有所改善,但它依然需要扫描
offset + limit
个索引项,所以在超大偏移量下,性能依然不如键集分页。

4. 缓存策略的引入: 对于那些数据不经常变动,但访问频率很高的分页查询结果,可以考虑在应用层(比如使用Redis)进行缓存。第一次查询时将结果缓存起来,后续请求直接从缓存中获取。但这需要处理好缓存的失效和一致性问题。

5. 业务需求与技术实现的权衡: 很多时候,用户真的需要跳转到“第500页”吗?还是说“上一页/下一页”的浏览体验已经足够?产品设计上,可以引导用户使用更符合键集分页特性的交互模式,比如无限滚动加载。这不仅能提升性能,也能带来更流畅的用户体验。我发现,很多时候所谓的“任意页码跳转”只是开发人员的惯性思维,而非用户的真实痛点。

6. 数据库层面的参数调整(谨慎使用): 虽然不是首要优化手段,但了解一些数据库参数对分页性能的影响也是有益的,例如

sort_buffer_size
read_rnd_buffer_size
等。这些参数会影响MySQL在排序和随机读取数据时的内存使用,但修改它们需要对数据库有深入理解,并进行充分测试,以避免引入新的性能问题。

总的来说,设计和优化大表分页查询,没有一劳永逸的方案,更多的是根据具体业务场景、数据量、访问模式以及对用户体验的期望,进行综合性的技术选型和优化。键集分页提供了一个非常高效的基线方案,而其他的策略则是为了弥补其不足或在特定场景下提供额外助力。

相关专题

更多
mysql修改数据表名
mysql修改数据表名

MySQL修改数据表:1、首先查看数据库中所有的表,代码为:‘SHOW TABLES;’;2、修改表名,代码为:‘ALTER TABLE 旧表名 RENAME [TO] 新表名;’。php中文网还提供MySQL的相关下载、相关课程等内容,供大家免费下载使用。

664

2023.06.20

MySQL创建存储过程
MySQL创建存储过程

存储程序可以分为存储过程和函数,MySQL中创建存储过程和函数使用的语句分别为CREATE PROCEDURE和CREATE FUNCTION。使用CALL语句调用存储过程智能用输出变量返回值。函数可以从语句外调用(通过引用函数名),也能返回标量值。存储过程也可以调用其他存储过程。php中文网还提供MySQL创建存储过程的相关下载、相关课程等内容,供大家免费下载使用。

246

2023.06.21

mongodb和mysql的区别
mongodb和mysql的区别

mongodb和mysql的区别:1、数据模型;2、查询语言;3、扩展性和性能;4、可靠性。本专题为大家提供mongodb和mysql的区别的相关的文章、下载、课程内容,供大家免费下载体验。

281

2023.07.18

mysql密码忘了怎么查看
mysql密码忘了怎么查看

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS 应用软件之一。那么mysql密码忘了怎么办呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

514

2023.07.19

mysql创建数据库
mysql创建数据库

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS 应用软件之一。那么mysql怎么创建数据库呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

255

2023.07.25

mysql默认事务隔离级别
mysql默认事务隔离级别

MySQL是一种广泛使用的关系型数据库管理系统,它支持事务处理。事务是一组数据库操作,它们作为一个逻辑单元被一起执行。为了保证事务的一致性和隔离性,MySQL提供了不同的事务隔离级别。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

386

2023.08.08

sqlserver和mysql区别
sqlserver和mysql区别

SQL Server和MySQL是两种广泛使用的关系型数据库管理系统。它们具有相似的功能和用途,但在某些方面存在一些显著的区别。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

530

2023.08.11

mysql忘记密码
mysql忘记密码

MySQL是一种关系型数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。那么忘记mysql密码我们该怎么解决呢?php中文网给大家带来了相关的教程以及其他关于mysql的文章,欢迎大家前来学习阅读。

599

2023.08.14

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

19

2026.01.20

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 8.8万人学习

Node.js 教程
Node.js 教程

共57课时 | 9万人学习

CSS3 教程
CSS3 教程

共18课时 | 4.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号