滚动新闻 →
密西西比州参议员科洛姆获川普公开肯定 民主党中期选举成果显现 SpaceX星舰第15次试飞突破轨道技术 机械哥斯拉首次亮相 唐元隽因涉嫌为中共搜集美国异议人士信息被纽约法院判缓刑三年 欧洲参与俄乌和谈遭分歧 战俘交换后冲突再升级 美国财长:伊朗黑客利用中国AI攻击法国超市冷藏系统 唐元隽涉非法搜集海外异议人士信息被判缓刑 双黄丑闻蔓延白宫 卢特尼克或因放水被问责 黄仁勋辞任事件引安全担忧 中共渗透手段升级 加拿大政界呼吁彻查中共海外警务据点 清华特等奖学金评选引争议 AI方向占比近全 前CIA局长之子因传播儿童色情制品被起诉 突发:新奥尔良港油轮撞上客船 多人落水 美国联邦航空总署实施全国航班停飞措施 退伍军人事务部长披露福利审批通过率高达94% 黄仁勋被美众议院三大委员会要求出席闭门听证会 法国10月通胀率突破3.7% 创近3年新高 山西男子砍伤妻岳母未被批捕 释放后勒死妻子引争议 黄仁勋辞职引台湾日本政界高度关注 唐元隽因向中共提供情报获三年缓刑 欧几里得望远镜发布首批观测数据 助力探索暗宇宙之谜 内蒙古化工厂氨气泄漏事件引争议,官方称属无毒催泪弹演练 英伟达共谍风暴升级 高阶芯片流向成焦点 西安村民因欠款问题冲击政务中心 无锡超算中心遭黑客入侵数据遭出售 网民质疑掩盖真相 Anthropic IPO超额认购六倍 加拿大各界在老兵纪念日缅怀阵亡将士并致敬军人 习近平称继承孙中山理念 学者质疑历史背离 孙中山纪念大会异常引关注 习健康状况引猜测 中国AI模型被指用于伊朗网络攻击 法国超市遭大规模破坏 雕刻大师现场展示技艺 纽约跨年水晶球亮相时代广场 Google Distributed Cloud 是什么,无法全部迁移到公有云的企业可以怎样部署 新疆库车市发生5.0级地震 乌鲁木齐民众普遍感知明显 美以海军高层会谈 土耳其卡塔尔签署防御协议 涉嫌为中共搜集情报 唐元隽纽约获三年缓刑 上海交大基因编辑实验致女童死亡 处罚仅限开除引争议 民主党参议院领袖选举引争议 范霍伦挑战舒默权威 江苏句容城管强抢餐车引摊贩冲突 中共借孙中山纪念进行统战操作 学者质疑继承论述模糊 黄乐与华为关联引调查 深圳数据中心牵出供应链疑云 京沪高铁山东段故障引争议 施工挖断电缆遭质疑

MySQL LIMIT 分页数据越来越慢,SaaS 后台应该怎样优化

发布时间: 2026-10-24 06:24:01    最后更新: 2026-11-13 13:28:24    阅读:214  约11 分钟阅读     

MySQL LIMIT 分页数据越来越慢,SaaS 后台应该怎样优化
图片说明:示意图   图片来源:Public Domain(公有领域)
很多SaaS后台在数据量较小时,使用LIMIT 0,20、LIMIT 1000,20甚至LIMIT 100000,20都没有明显感觉。随着订单、用户、日志、工单等数据不断增加,深分页开始出现明显的延迟。用户翻到第几千页以后,页面可能从几十毫秒变成几百毫秒甚至几秒。

这通常不是MySQL突然“变慢”了,而是传统OFFSET分页的工作方式决定了它在深分页场景下成本会不断增加。

例如查询:

SELECT id, created_at, status
FROM orders
WHERE tenant_id = 1001
ORDER BY id
LIMIT 100000, 20;

数据库需要找到满足条件并按照指定顺序排列的记录,然后跳过前面的100000条,最后返回20条。即使查询能够利用索引,数据库仍然可能需要扫描大量索引记录才能到达真正需要返回的位置。

因此,深分页真正的问题不是“LIMIT本身很慢”,而是OFFSET越大,需要处理并丢弃的前置记录越多。它通常表现为扫描行数增加、CPU和I/O压力增加、查询延迟上升,而不是简单的“指数级增长”。

如果后台经常出现这种查询,第一步不是马上上Redis、Elasticsearch或者分库分表,而是先检查查询条件、排序方式和索引是否匹配。

对于典型SaaS多租户系统,例如:

SELECT id, created_at, status
FROM orders
WHERE tenant_id = ?
ORDER BY created_at DESC, id DESC
LIMIT 20;

如果这是高频查询,一个常见的索引设计是:

CREATE INDEX idx_orders_tenant_created_id
ON orders (tenant_id, created_at, id);

这样数据库可以先限定租户,再按照排序字段读取数据。索引是否真正有效,还应该通过EXPLAIN或者MySQL 8中的EXPLAIN ANALYZE验证,而不是看到一个索引就认为问题已经解决。

索引字段顺序同样重要。SaaS系统通常首先需要解决租户隔离,因此大量查询都会带有tenant_id。如果业务模式是“一个租户查询自己的订单,再按照创建时间排序”,那么(tenant_id, created_at, id)往往比单独建立created_at索引更符合实际访问路径。

对于返回大量列表字段的查询,还可以考虑覆盖索引,但不能为了“避免回表”就把几十个业务字段全部塞进索引。索引越宽,写入、更新、缓存空间和维护成本也越高。最终应该根据真实查询字段和EXPLAIN ANALYZE结果决定。

当OFFSET开始成为主要成本时,更适合SaaS后台的办法通常是Keyset Pagination,也就是通常所说的Cursor Pagination。这里的“Cursor”不是MySQL内部的数据库游标,而是一种利用上一页最后一条记录作为下一页边界的分页方式。

例如按照递增ID查询:

SELECT id, created_at, status
FROM orders
WHERE tenant_id = ?
AND id > ?
ORDER BY id ASC
LIMIT 20;

上一页最后一条记录的ID就是下一页的游标。数据库不需要从第100000条开始数,而是可以直接从索引中的这个位置继续读取20条记录。

如果后台按照时间倒序排列,则应该让游标与排序字段保持一致。例如:

SELECT id, created_at, status
FROM orders
WHERE tenant_id = ?
AND (
created_at < ?
OR (created_at = ? AND id < ?)
)
ORDER BY created_at DESC, id DESC
LIMIT 20;

相应的索引可以设计为:

CREATE INDEX idx_orders_tenant_created_id
ON orders (tenant_id, created_at, id);

这里为什么还需要id?因为created_at可能相同。单独使用时间作为游标,在大量记录拥有相同时间戳时可能出现重复或者漏记录。把created_at和唯一性更好的id组合起来,可以形成稳定的排序边界。

Keyset Pagination非常适合订单列表、消息列表、操作日志、工单、交易记录以及无限滚动等场景。但它也有明显限制:如果用户要求“直接跳到第500页”,Cursor分页并不天然适合。传统OFFSET在这种需要任意页码跳转的后台报表中仍然有使用价值,只是不能让它无限深入。

因此,实际SaaS系统往往采用混合策略。首页和前几页使用普通LIMIT分页并没有问题;当业务不需要显示总页数、用户主要进行下一页或上一页浏览时,可以逐步采用Keyset Pagination;如果必须支持任意页码跳转,则应该重新评估这个页面是否真的需要传统分页。

缓存也不能简单理解为“把分页结果全部放进Redis”。分页结果高度依赖租户、筛选条件、排序方式和数据更新时间。用户随便修改一个筛选条件,就可能产生完全不同的缓存Key。如果数据变化频繁,大量分页缓存还会带来失效和内存管理问题。

Redis更适合缓存真正稳定、访问频率高、计算成本明显的数据,而不是机械地缓存所有分页结果。对于首页统计、热门数据、权限信息、配置数据等场景,缓存通常更加容易获得收益。对于实时变化的订单列表,首先应该把SQL和索引做好。

SaaS系统还需要特别注意租户隔离。一个查询如果理论上只应该看到当前租户的数据,那么tenant_id不能只是应用层“记得加”的一个条件,而应该成为整个查询、索引和权限设计的一部分。

例如:

SELECT id, created_at, amount
FROM orders
WHERE tenant_id = ?
AND status = ?
ORDER BY created_at DESC, id DESC
LIMIT 50;

对应的索引可能是:

CREATE INDEX idx_orders_tenant_status_created_id
ON orders (tenant_id, status, created_at, id);

具体字段顺序仍然要根据真实查询模式决定。不要因为网上某个案例使用了四列联合索引,就照搬到自己的数据库。

至于“每个租户一个Schema”或者“按照租户进行数据库分区”,也不能作为SaaS分页优化的标准答案。大量租户的小数据场景下,统一表加tenant_id通常更容易管理;只有当租户规模、数据隔离、备份恢复、合规要求或者数据分布已经达到一定程度时,才需要重新考虑Schema、数据库实例、分区或者分片。

读写分离同样不是解决深分页的直接办法。如果SQL本身需要扫描大量索引记录,把它从主库移动到只读副本,只是把压力转移了。更重要的是,复制存在延迟时,用户刚写入的数据可能暂时在读库中看不到。因此读写分离应该服务于整体数据库负载,而不是作为LIMIT分页慢的第一解决方案。

Elasticsearch或OpenSearch也不应该因为“数据超过百万行”就自动加入系统。MySQL本身完全可以处理大量结构化列表查询,关键取决于数据规模、查询模式、索引设计、并发量以及硬件。只有当业务真正需要全文搜索、复杂文本相关性、模糊检索、聚合搜索或者跨字段搜索,并且MySQL已经无法经济地承担这些查询时,搜索引擎才有明确价值。

同样,分区表、分库分表、TiDB等分布式数据库也不是深分页的直接药方。如果一个查询本身没有合适索引,那么把数据库变成分布式系统以后,问题甚至可能变得更加复杂。

实际优化时,建议先打开慢查询日志,观察真实SQL,再使用EXPLAIN和EXPLAIN ANALYZE查看执行计划。重点关注访问类型、实际扫描行数、返回行数、排序、临时表以及实际执行时间。

如果一个页面只需要20条记录,却扫描几十万甚至几百万条索引记录,那么问题已经非常清楚。此时应该优先检查WHERE条件、ORDER BY、联合索引以及分页方式,而不是先增加缓存服务器。

压力测试也应该模拟真实访问模式。不要只测试第一页,因为第一页通常恰恰是最容易查询的。应该同时测试第一页、第100页、第1000页以及典型的Cursor位置,并观察P50、P95和P99延迟,同时记录CPU、磁盘I/O、Buffer Pool命中情况、数据库连接数以及扫描行数。

对于一个成熟的SaaS后台,分页优化通常可以形成一个比较清晰的演进路径:先保证租户条件正确,再让WHERE和ORDER BY与索引匹配;随后使用EXPLAIN ANALYZE验证执行计划;当深分页成为实际瓶颈时,把适合的列表改成Keyset Pagination;只有在真实负载证明需要时,再考虑Redis、读写分离、搜索引擎、分区或者分布式数据库。

分页技术没有一个适用于所有SaaS系统的“标准答案”。数据量只有几万行、查询并发很低的后台,没有必要为了避免OFFSET而把系统搞成复杂的分布式架构;而每天产生数百万条订单或日志、用户又频繁翻阅历史数据的系统,则应该尽早让分页方式与索引结构一起演进。

真正需要优化的并不是LIMIT这个SQL关键字,而是数据库为了找到这一页数据到底做了多少无效工作。让索引直接把数据库带到目标位置,再让查询只读取真正需要的数据,通常比不断增加缓存、服务器和中间件更加直接。
本文作者
程越(Kevin Cheng)
IT与人工智能技术资深编辑

喜欢这篇报道?

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

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

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