滚动新闻 →
阿拉斯加惊现水母状UFO 空中停留3分钟 MySQL 分页查询怎么设计?从传统分页到高效分页逐步理解 外媒点名英伟达CEO接班人 黄仁勋下台风险加剧 巴黎春天百货圣诞橱窗再现汤姆杰瑞打毛衣童趣场景 美中峰会前夕卢比奥与王毅通话 各自表述立场 双11消费热度下降 中产阶层消费趋于理性 退伍军人节川普万斯献花 致敬军人保卫国家 加拿大总理将就中共干涉问题与中共党魁会面 美俄乌三方会谈未达共识,特使称双方信任缺失 以色列或重启对伊军事行动 川普与内塔尼亚胡分歧加剧 美富裕家庭子女数量超中产,生育趋势出现新变化 纽约布鲁克林区三层建筑坍塌 至少1人遇难 35岁喜剧演员阿颜手术中突发心脏骤停 生前留言成遗言 古典文学里的长安为何如此重要 纽约布鲁克林三楼建筑坍塌 墙体崩落暴露卧室衣柜致1死12伤 美中AI对话将启 贝森特称分歧无法回避 黄仁勋事件折射美中科技博弈 古代人为什么重视穴位 俄驻美大使达尔奇耶夫逝世 俄方未公布死因 秋日玫瑰园绽放 京成玫园迎盛景 三星杯:申真谞险胜芝野虎丸 许皓鋐挺进16强 意大利六千多所学校遭遇炸弹威胁 总理召开紧急会议 密西西比参议院席次悬而未决 阿拉斯加邮寄选票引法律争议 黄仁勋婚外情风波引英伟达高层震荡 中国篆刻艺术为什么重视刀法 台湾高检署破获境外介入选举案 查扣逾4亿不法资金 黄仁勋婚外情风波后 英伟达股价今日回升 美国制裁后 西北工业大学仍采购H200芯片 APEC前夕深圳烂尾楼披‘遮羞布’引争议 黄仁勋婚外情涉国安调查 五大疑点待解 黄乐涉中共情报案引科技窃密争议 美中俄三方会谈形式分歧 川普拒绝按脚本进行 纽约老兵节游行致敬退伍军人 传承爱国精神 孩子生气时喜欢伤害家人怎么办 法拉盛日托主管承认涉1.2亿美元医保欺诈罪名 视频为什么不能一直使用相同镜头 民国时期军阀割据是如何形成的 川普政府为何与中共建立超级智能沟通机制?美中科技竞争正在划定新的谈判边界 阳台柜应该如何规划 川普出席老兵节仪式 献花圈缅怀阵亡将士

MySQL 分页查询怎么设计?从传统分页到高效分页逐步理解

发布时间: 2026-11-11 21:00:03    最后更新: 2026-11-11 21:44:45    阅读:8  约16 分钟阅读     

MySQL 分页查询怎么设计?从传统分页到高效分页逐步理解
图片说明:示意图   图片来源:Public Domain(公有领域)
对于新闻网站、电商平台和内容管理系统而言,分页查询不仅关系到页面能否正常显示,还直接影响数据库的索引利用率、磁盘读取量、服务器负载以及用户浏览体验。当一张数据表只有几千条记录时,使用 LIMIT 和 OFFSET 通常就能满足需求;但随着文章数量增长到几十万条甚至数百万条,深分页可能逐渐成为数据库性能瓶颈。如何在保持查询结果正确的同时减少不必要的数据读取,需要结合索引结构、排序规则、数据更新频率和业务访问方式进行设计。

一、传统分页为什么会越来越慢

MySQL 最常见的分页方式是使用 LIMIT 配合 OFFSET。例如,一个商品列表每页显示10条记录,需要查询第三页时,可以使用以下SQL语句:

SELECT id, name, price
FROM products
WHERE category = '电子产品'
ORDER BY id DESC
LIMIT 20, 10;

这里的 LIMIT 20, 10 表示跳过前20条符合条件的记录,再返回接下来的10条。MySQL 也支持更直观的写法 LIMIT 10 OFFSET 20,两种写法表达的是相同的分页要求。

这种方法的优势是实现简单,能够直接支持第一页、第二页、第十页等任意页码,也便于计算总页数和构建页码导航。对于后台管理系统、规模不大的商品目录以及需要直接跳转到指定页码的文章列表,传统分页仍然具有实际价值。

真正的问题出现在深分页。例如:

SELECT id, title, created_at
FROM news
ORDER BY id DESC
LIMIT 99990, 10;

假设查询结果按照 id 降序排列,这条语句需要跳过前面的99990条记录,再返回10条。即使 id 上存在合适的索引,数据库通常仍然需要沿着索引读取并越过大量记录,才能到达目标位置。OFFSET 越大,需要处理的索引记录通常也越多。

需要注意的是,深分页并不意味着 MySQL 必然进行全表扫描。如果排序字段有合适的索引,查询可能主要沿着索引扫描;如果查询条件与排序方式不匹配,则可能出现大量行读取、额外排序或临时表操作。具体成本取决于执行计划、数据分布、索引结构以及是否需要回表读取其他字段。

因此,不能简单地把10万条记录当作传统分页的性能分界线。即使只有几万条记录,如果查询条件复杂、索引缺失或返回大量字段,也可能出现性能问题;反过来,经过合理优化的百万级数据表,在浅分页场景下仍然可能运行良好。

二、索引优化是分页设计的第一步

在改用更复杂的分页方案之前,应先检查查询条件和排序字段是否能够有效利用索引。

例如,商品列表通常根据 category 筛选商品,再按照 id 从大到小排列,可以建立联合索引:

CREATE INDEX idx_category_id
ON products (category, id);

对于以下查询:

SELECT id, name, price
FROM products
WHERE category = '电子产品'
ORDER BY id DESC
LIMIT 10;

联合索引首先按照 category 排列,同一类别内再按照 id 排列。由于 category 是等值查询条件,MySQL 可以利用索引定位对应类别的数据,并按照 id 的顺序读取记录。在支持反向索引扫描的 MySQL 版本和适用的执行计划中,降序读取也不一定需要额外排序。

不过,联合索引并非越多越好。索引会占用磁盘空间,也会增加 INSERT、UPDATE 和 DELETE 的维护成本。如果查询经常只使用 id 排序,而不限制 category,就需要根据实际访问模式评估是否还需要单独的 id 索引。

另一个容易被忽略的问题是 SELECT *。当一条记录包含长文本、JSON、图片地址或其他较大的字段时,直接读取全部列可能增加磁盘 I/O、缓冲池压力和网络传输量。列表页面通常只需要文章 ID、标题、发布时间和少量摘要信息,没有必要把完整正文一起取出。

判断索引是否真正发挥作用,应使用 EXPLAIN 检查执行计划,并结合慢查询日志观察实际耗时、扫描行数和访问频率。对于重要查询,还可以在适用的 MySQL 版本中使用 EXPLAIN ANALYZE,观察实际执行过程,而不是仅凭 SQL 语句表面上的简洁程度判断性能。

三、游标分页如何避免深度 OFFSET

当数据规模较大,而且用户通常按照时间或唯一 ID 向后浏览时,可以使用基于游标的分页,也称为 Keyset Pagination。

假设商品按照 id 降序排列,第一页查询10条记录:

SELECT id, name, price
FROM products
WHERE category = '电子产品'
ORDER BY id DESC
LIMIT 10;

假设这一页最后一条记录的 id 是10000,下一页便可以使用该记录作为游标:

SELECT id, name, price
FROM products
WHERE category = '电子产品'
AND id < 10000
ORDER BY id DESC
LIMIT 10;

第二页不再要求数据库跳过前面所有记录,而是直接从符合条件的索引位置继续读取。对于具有合适索引的查询,这种方式通常能够显著降低深分页的扫描成本。

如果联合索引是 (category, id),上述查询的筛选条件和排序字段就能够较好地配合索引。游标分页的性能优势来自定位方式的变化,而不是游标本身具有特殊的加速能力。如果没有合适的索引,或者排序字段无法有效利用索引,游标分页同样可能很慢。

实现时,应该保存上一页最后一条记录的排序键,并使用严格的小于或大于条件确定下一页的起点。例如,降序查询使用 id < last_id,可以避免把上一页最后一条记录再次返回。首次请求则不需要游标条件,而不是简单地把所有场景中的游标默认设置为0。

游标分页尤其适合新闻时间线、日志列表、评论流和无限滚动页面。但它也有明显限制:用户通常不能直接跳到第100页,因为每一页的起点依赖前一页的结果。如果业务要求随意输入页码、准确显示总页数或跳转到指定页,就需要继续使用传统分页,或者设计额外的定位机制。

四、按照发布时间分页,需要处理相同时间的问题

新闻网站和日志系统往往按照发布时间排列,而不是单纯按照自增 ID 排序。此时可以使用 create_time 作为主要排序字段,但如果多条记录具有相同的时间戳,只使用时间作为游标就可能导致重复或遗漏。

例如,下面的查询使用发布时间降序排列:

SELECT id, title, created_at
FROM news
ORDER BY created_at DESC, id DESC
LIMIT 10;

这里同时使用 created_at 和 id 排序。发布时间决定主要顺序,id 则作为第二排序键,确保相同时间的记录仍然具有确定的先后顺序。

假设上一页最后一条记录的发布时间是 2026-10-07 12:00:00,ID为5000,下一页可以这样查询:

SELECT id, title, created_at
FROM news
WHERE created_at < '2026-10-07 12:00:00'
OR (
created_at = '2026-10-07 12:00:00'
AND id < 5000
)
ORDER BY created_at DESC, id DESC
LIMIT 10;

这种条件表示:读取发布时间更早的记录,或者读取发布时间相同但 ID 更小的记录。它将两个排序字段组合成一个明确的游标边界,避免只使用时间戳时发生分页边界错误。

可以考虑建立与查询模式对应的联合索引:

CREATE INDEX idx_news_created_id
ON news (created_at, id);

如果查询还需要筛选 publish_status、category 等字段,索引设计则应结合真实 SQL 和数据分布进一步调整。不能机械地把所有 WHERE 字段都放进索引,因为联合索引的字段顺序会影响索引能否用于定位、排序和范围扫描。

此外,游标分页并不自动保证整个浏览过程看到的是同一时刻的数据。如果浏览期间有新文章发布、记录被删除,或者排序字段发生修改,后续页面仍可能与第一次请求时的完整数据集合不同。对于普通新闻浏览,这通常可以接受;对于财务记录、审计数据或要求严格一致性的导出任务,则应考虑一致性快照、事务隔离级别或固定的数据截止边界。

五、只取 ID 再读取正文,能否改善深分页

对于需要保留页码跳转的传统分页,还可以采用延迟读取,也称为 Deferred Join 或延迟关联。核心思路是先通过索引找到目标页的 ID,再读取这批记录的其他字段,减少深分页阶段不必要的宽记录访问。

例如:

SELECT n.id, n.title, n.created_at
FROM news AS n
JOIN (
SELECT id
FROM news
ORDER BY id DESC
LIMIT 99990, 10
) AS page_ids
ON n.id = page_ids.id
ORDER BY n.id DESC;

内层查询只获取 ID,外层再根据 ID 读取标题和发布时间。对于包含长正文或大量字段的表,这种方法可能减少回表读取的开销,尤其是在目标查询原本需要处理较宽的记录时。

不过,延迟关联并没有消除 OFFSET 本身的成本。内层查询依然需要跳过99990条记录,因此它不能完全解决深分页随页码增加而变慢的问题。如果页面数据量很大,且用户主要是顺序向后浏览,游标分页通常更值得优先考虑;如果必须支持任意页码,延迟关联则可以作为传统分页的优化手段之一。

实际使用时,还应保证内外层查询采用一致的筛选条件。假如文章列表只显示已发布内容,内层查询就应该在选择 ID 时过滤发布状态,否则可能先选出大量不应展示的记录,导致最终页数不足或结果不符合预期。

六、分页总数、缓存和并发更新如何处理

很多网站不仅需要返回当前页的数据,还需要显示总记录数和总页数。传统页码导航经常通过 COUNT(*) 获取符合条件的记录数量,再计算总页数。但对于复杂筛选条件和大型数据表,精确统计本身也可能消耗较多资源。

如果产品设计并不要求精确显示总页数,可以只查询当前页需要的数据,并额外多取一条记录。例如每页显示10条,就查询11条:如果实际返回11条,说明通常还有下一页;如果只有10条或更少,则可以据此判断是否还有更多数据。这样能够避免每次翻页都执行精确计数,但它无法提供精确的总记录数。

缓存也可以降低热门分页请求对数据库的压力。对于访问量较高、更新频率较低的分类页面,可以缓存一段时间内的查询结果,并在文章发布、删除或分类变化时更新或失效相关缓存。缓存键应包含影响结果的筛选条件、排序方式、页码或游标等信息,避免不同查询错误地共用同一份结果。

然而,缓存并不是所有分页查询的必要条件。对更新频繁的新闻首页,过度缓存可能导致最新文章无法及时显示;对冷门页面,缓存带来的收益也可能有限。应根据访问频率、查询成本、数据更新规律和可接受的延迟制定缓存策略,而不是仅仅因为数据库记录多就引入 Redis。

并发更新则是另一个需要考虑的问题。使用 OFFSET 时,如果新记录插入到列表前端,用户翻到下一页时可能再次看到上一页出现过的记录;删除记录也可能使某些条目被跳过。游标分页通过固定排序键继续读取,通常能减少这种由前端插入导致的位移问题,但它仍不能自动提供完整的一致性快照。对于需要严格复现某一时刻数据集合的业务,应当明确数据快照或截止边界,而不能仅依赖分页算法。

七、PHP 网站应该怎样选择分页方案

在 PHP 网站中,分页设计应同时考虑数据库性能、前端交互和业务要求。传统页码分页适合后台管理、需要任意页码跳转的文章目录和规模适中的分类列表;游标分页更适合新闻流、日志、评论和无限滚动;延迟关联则适用于必须保留传统页码导航、同时希望减少深分页宽记录访问的场景。

以 PDO 为例,查询条件和游标值应使用预处理语句绑定,而不能直接把用户输入拼接到 SQL 中。对于 LIMIT 和 OFFSET 这类整数参数,应先验证并转换为合法的非负整数;对于动态排序字段,则应从服务器端预先定义的白名单中选择,因为列名和 ASC、DESC 等 SQL 结构通常不能像普通值一样通过占位符安全绑定。

例如,游标查询可以写成:

$sql = "
SELECT id, title, created_at
FROM news
WHERE publish_status = :status
AND id < :last_id
ORDER BY id DESC
LIMIT 10
";

$stmt = $pdo->prepare($sql);
$stmt->bindValue(':status', 'published', PDO::PARAM_STR);
$stmt->bindValue(':last_id', $lastId, PDO::PARAM_INT);
$stmt->execute();

$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

这段示例假设 $lastId 已经根据请求情况完成初始化和验证,并且使用 ID 作为稳定的降序游标。首次访问时,应使用不带游标条件的第一页查询,或由后端明确区分首次请求与后续请求。若改为按照发布时间和 ID 联合排序,就必须同步调整 SQL 条件、索引和游标内容,不能只修改 ORDER BY。

对于公开接口,还应避免完全信任客户端提交的游标。可以将排序键、筛选条件或版本信息封装在经过服务器验证的游标令牌中,防止客户端随意修改参数造成异常查询。即便使用签名令牌,也仍然需要进行权限检查、参数校验和合理的查询限制。

八、分页性能优化应从真实执行计划出发

分页设计没有适用于所有网站的唯一答案。判断方案是否合适,首先应明确数据表规模、访问模式、筛选条件、排序规则以及用户是否需要任意页码跳转,然后利用 EXPLAIN、慢查询日志和实际负载测试验证索引与查询方案。不能仅凭表中有多少条记录,就决定必须使用游标分页或缓存。

对于一般的文章列表,使用 LIMIT 和 OFFSET 并配合适当索引,通常已经足够。对于需要持续向后浏览的大型数据集,基于唯一排序键的游标分页能够避免深度 OFFSET 的主要开销。对于必须支持深分页的页码导航,可以评估延迟关联、限制最大页数或设计专门的定位方案。至于高并发场景,还需要结合缓存、连接池、查询监控和数据库资源配置综合优化。

真正可靠的分页系统,不是简单地把一种 SQL 写法替换成另一种,而是让查询条件、索引、排序、数据一致性和用户交互形成协调的整体。只有通过实际执行计划和负载数据验证优化效果,才能在查询速度、开发复杂度与维护成本之间找到适合具体业务的平衡。
程越(Kevin Cheng)
本文作者
程越(Kevin Cheng)
IT与人工智能技术资深编辑
程越专注于云计算、人工智能架构、软件工程与 SaaS 技术,长期关注大型云平台、AI基础设施、数据中心、模型部署、API、数据库及分布式系统。他熟悉主流编程技术与现代软件开发体系,并关注生成式AI、企业级AI应用以及AI与传统软件产业的融合。

他的技术分析注重底层架构与实际应用,重点解析AI技术、云计算和SaaS商业模式背后的技术逻辑、成本结构、竞争格局及发展趋势,为读者理解快速变化的科技产业提供专业视角。

喜欢这篇报道?

使用下面的功能,方便以后继续阅读和分享 MNewsTV

设为 Google 新闻首选来源 让 Google 新闻优先显示 MNewsTV 的最新报道 ›
★ 我的收藏 查看已经收藏的文章
关于文章收藏 收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。 删除收藏请进入「我的收藏」进行管理。
分享这篇报道

捐助(Paypal): https://www.paypal.me/observeccp
订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP
MNewsTV 栏目导航

栏目导航

探索不同领域的新闻、知识、财经、科技、历史与北美生活资讯。