在开发联调环境里一切响应飞快,数据量一上来,曾经“能用”的查询瞬间变成性能瓶颈,这类问题里最具代表性的就是分页查询。很多系统的线上故障,不是被复杂统计拖垮的,而是被一条简单的LIMIT 100000, 20拖垮的——这看起来不可思议,却很常见。尤其是早年用MySQL搭起来的业务库,单表几百万行到几千万行数据,一旦涉及深分页,那条曾经“还行”的SQL就像换了一台机器在跑。
我早些年在一家做电商中台的公司,销量表在各平台大促后轻轻松松突破千万级。后台订单管理页每翻一页,接口就要卡两三秒,客服一边骂系统一边清缓存,导致问题雪上加霜。后来仔细排查才发现,罪魁祸首几乎都是同一个:深分页查询。
这篇文章不打算讲那些教科书式的理论,而是把我这几年在真实业务里踩过的坑、验证过的方案和优化后的数据变化,结合常见业务场景重新梳理一遍。我会从“为什么慢”的根源讲起,再给出行之有效的几种优化方案,最后通过一个模拟项目的优化过程把这些手段串起来。如果你手头正好也有一个即将面临数据量增长的系统,或者正在为慢分页接口发愁,这篇文章应该能提供方向。
1. 先把问题拆明白:分页查询到底慢在哪里
1.1 不仅仅是“取数据”慢,而是“丢弃数据”慢
很多人一提到分页慢,第一反应是数据量大、索引没建好。但其实LIMIT 100000, 20这类深分页查询慢,本质上是MySQL在做一种非常“笨”的操作:先找到前100020条满足条件的记录,再丢掉前100000条,最后只把20条返回给客户端。整个过程中,真正有用的只有最后那20条,前面那10万条几乎全是为了“翻到这一页”而做的铺垫。
在没有合适索引辅助的情况下,这个“找前100020条”的操作会让InnoDB存储引擎全表扫描。即便有索引,如果查询条件和索引匹配度不高,依然会产生大量随机IO。随机IO对磁盘来说是最“伤”的操作,因为每一次跳转都需要寻道时间。机械硬盘的寻道时间在毫秒级,即使换成SSD,随机读相比顺序读也有明显差距。
这里可以拿一个简单的场景来理解:想象你在一个有几百万本书的图书馆里,管理员需要从第一本书开始数,数到第100001本才把后面的20本拿给你。这个管理员再勤快,也没办法跳过前面那10万本直接取最后的20本。
1.2 MySQL的“取-排-丢”三步曲
当一条带ORDER BY和LIMIT的SQL被执行时,MySQL内部大致会经历下面几个过程:
- 根据查询条件筛选出满足条件的全部记录。如果条件能命中索引,这个过程会快一些;如果没有合适的索引,就是全表扫描,每一条记录都要过一遍。
- 对筛选出的记录按
ORDER BY字段排序。如果排序字段能用索引直接提供顺序,就可以省去显式排序;否则,MySQL需要借助临时文件来做归并排序或堆排序。 - 从排序结果中取出偏移量之后的所有记录,再把偏移量之前的记录丢弃。这个步骤完全是浪费,但因为SQL语义如此,MySQL必须老老实实从头开始数。
这有点像是工厂流水线:商品从传送带一头进入,走到指定位置才把需要的几个拿下来,其他所有通过的商品都得“经过”一遍,当通过成本很高时,最后的“丢弃”动作就显得格外昂贵。
1.3 覆盖索引能解决什么,不能解决什么
为了减少回表造成的随机IO,很多人会想到利用覆盖索引——也就是让索引中包含查询需要的所有字段,查询时直接从索引树里取数据,避免再到聚簇索引(主键索引)里回查一次完整行记录。这个思路本身是正确的,对查询效率的提升也往往是肉眼可见的。
但覆盖索引对深分页问题只是“缓解”,不是“根治”。它能让“找到前100020条记录”这一步走得更快,因为索引树的扫描通常比全表扫描代价小很多,可MySQL仍然需要从第一行开始数,仍然需要丢掉前100000条,只是把丢数据前的准备工作压缩了一下。
光靠覆盖索引一个手段,或者建几个普通索引,解决不了海量数据下深分页的根本矛盾。真正要做的,是换一种思路,让自己尽量少做“无用功”。
2. 两种行之有效的基础优化方案
2.1 方案一:延迟关联——先把主键找出来,再回表取数据
延迟关联(Deferred Join)的思路很直接:分页的时候,第一步先用覆盖索引查出当前页需要的主键ID,第二步再用这些ID去关联完整表,取出详细字段。这种方式能有效避免MySQL在深分页时对大字段进行无效回表。
用一段SQL来说明。
假设场景是一张订单表orders,字段包括主键id、用户IDuser_id、下单时间order_time、订单金额amount、支付状态pay_status等。订单量已经有三千万行,客服后台需要按时间倒序查看订单。
原始写法可能是这样的:
SELECT id, user_id, order_time, amount, pay_status FROM orders WHERE pay_status = 1 ORDER BY order_time DESC LIMIT 100000, 20;如果order_time上有索引,看起来好像没问题,但MySQL要先按索引过滤出所有pay_status = 1的记录(这是一次索引范围扫描),然后按order_time排序(如果顺序不一致还需要临时排序),再跳过10万条,回表取出100020条完整记录,最后只留20条。整个过程笨重且浪费。
优化成延迟关联的写法是这样的:
SELECT o.id, o.user_id, o.order_time, o.amount, o.pay_status FROM ( SELECT id FROM orders WHERE pay_status = 1 ORDER BY order_time DESC LIMIT 100000, 20 ) AS tmp JOIN orders AS o ON tmp.id = o.id ORDER BY o.order_time DESC;内层查询只查id,搭配(pay_status, order_time, id)这样的复合索引,可以完全走覆盖索引,不需要回表。内层返回的只有20个主键ID,外层再拿这20个ID去聚簇索引里直接取值。虽然外层的一次回表是不可避免的,但回表次数从10万次降到了20次,这个差距就是性能提升的最大来源。
在我模拟的测试环境里,三千万行数据下,这条SQL的耗时从原来的1.2秒左右降到了100毫秒以内。效果立竿见影。
2.2 方案二:游标分页——不做深分页,改记位置
如果说延迟关联是在“不改变查询形态”的前提下做优化,那么游标分页(Keyset Pagination)则是彻底改变了查询形态。它的核心思想很简单:每一页请求都携带上一页最后一条记录的某个唯一标识,然后用这个标识作为过滤条件,把确定不需要的数据直接跳过。
最典型的游标分页写法如下:
SELECT id, user_id, order_time, amount, pay_status FROM orders WHERE pay_status = 1 AND order_time < '2024-06-01 12:00:00' ORDER BY order_time DESC LIMIT 20;上一页返回的最后一条记录的order_time是2024-06-01 12:00:00,下一页查询直接就从这个时间点以前开始取。因为order_time上有索引,MySQL只需要定位到“书架”的指定位置,然后顺着索引顺序往后取20条,完全不需要从头数。
但这里有一个重要前提:排序字段必须是唯一且有序的。如果只用order_time排序,当两条记录具有完全相同的order_time时,翻页就会出现问题——上一页没取完的数据在下一页可能会被跳过,或者重复出现。
最稳妥的方式是使用“唯一字段 + 业务排序字段”的组合游标。例如按(order_time, id)复合排序,那么下一页的过滤条件就要带上两个字段:
WHERE pay_status = 1 AND (order_time < '2024-06-01 12:00:00' OR (order_time = '2024-06-01 12:00:00' AND id < 100320)) ORDER BY order_time DESC, id DESC LIMIT 20;这种写法把排序字段组成了一个“联合游标”,只要(order_time, id)上有复合索引,MySQL就能准确定位到上一页的结束位置,从这个位置开始继续向后取数据。
2.3 两种方案怎么选
延迟关联适合改动范围小、不想动前端交互逻辑的项目。它保留了页码跳转的能力,用户仍然可以点击第100页、第1000页。缺点在于如果你真的翻到第100万页,延迟关联也只能把损耗降低,没办法完全消除“从头数”的开销,所以它更适合中等深度的分页场景。
游标分页是真正意义上的“无限翻页”方案,不管往后翻多少页,耗时都基本稳定。它的代价也很明显:用户无法直接跳转到指定页数,交互上比较适合“加载更多”“上一页/下一页”这类模式,而不适合传统页码导航。
现实业务里,电商订单管理、内容流、搜索结果这类场景大多可以接受游标分页。像传统的表格型报表、后台列表页,往往更依赖页码跳转,这时候可以先用延迟关联过渡,再配合后续章节提到的其他手段。
提示:游标分页还有一个问题在真实系统中经常被忽略——跨页数据变动。如果上一页返回了10条数据,这期间新增了一条记录,下一页的“位置”会整体下移,可能出现重复数据。业务上如果对数据一致性要求极高,需要考虑快照读或加锁方案。
3. 当数据量再上一个台阶:索引设计、分表分区与多级缓存
3.1 联合索引的正确设计与“魔鬼细节”
很多时候分页慢不是方案不对,而是索引根本就没建对。比如WHERE pay_status = 1 ORDER BY order_time DESC这条查询,最合适的索引应该是(pay_status, order_time),而不是单独在order_time上建索引。
在联合索引中,MySQL会先按pay_status定位到所有需要的数据范围,再在这个范围内用order_time排序,如果两者组合成了索引的第二列,那么排序就不需要额外做文件排序,顺序读取就能直接返回结果。这是“索引即排序”的关键。
单独在order_time上建索引,MySQL需要先扫一遍索引找到所有满足pay_status = 1的记录,再回表确认各自的状态,最后还得做一次排序。这里又多了回表,又多了临时排序,代价自然高。
这条经验几乎适用于所有分页场景:凡是查询条件里有等值条件又有排序字段,优先考虑把等值字段放前面、排序字段放后面的联合索引。
3.2 分表分区:解决“单表太大”的极端场景
表数据量过了几千万还能忍,但如果到了几亿、几十亿的规模,单表怎么都跑不动了。这种量级下,分表或分区就成了必然选项。
分区表能把一张大表在逻辑上拆成多个物理存储区域,查询时可以根据分区键直接裁剪掉大量无用数据。比如按order_time做RANGE分区,只查某一个月的数据时,MySQL可以直接跳过其他月份的分区文件,扫描量大幅下降。
但分区方案也有自己的坑。如果分区键和业务查询条件不匹配,比如只按order_time分区,却经常按user_id查询,MySQL可能无法有效裁剪分区,最终一次查询扫遍所有分区文件,反而比单表还慢。设计分区前,一定要先统计线上的真实查询模式。
分表是更彻底的做法。比如按用户ID哈希分成64张表,把订单分别路由到对应分表里。传统业务的订单查询通常是按用户维度发起的,所以按用户分表在架构上最自然。但分表带来的问题也不少:跨表翻页变得困难,排序必须在应用层做归并,count需要聚合所有分表的统计结果,运维复杂度也跟着指数级上升。除非数据量真的到了非分不可的地步,我通常建议先做索引优化和读写分离,把分表作为最后手段。
3.3 缓存介入:用Redis挡住最热的深分页页尾
在一些实际项目里,访问热度的分布极度不均匀。比如订单列表页,用户通常翻前几页,翻到20页以后的流量就很少了。最底部的页尾数据虽然偶尔有人访问,但一旦触发,就是一次深分页扫描,成本非常高。
对这类场景,可以用Redis做多级缓存。比如把前N页的查询结果缓存起来,热点请求直接走缓存;针对最深的页尾,设置一个失效时间较长的缓存,因为它的数据变更频率反而比首页低很多。
对我来说比较实用的一种做法是:热点页缓存失效,但深页缓存保留,配合后台异步更新。也就是说,当有人请求第100页时,先查Redis,如果没有就执行SQL,并把结果写入缓存,设置一个适中的过期时间。首页数据因为变化频繁,缓存很容易失效,但如果第100页的数据在业务上没有频繁变动,这个缓存实际上能存活很久,后续请求全部由Redis扛住,数据库的深分页压力几乎为零。
当年做订单管理后台时,我用这种方法把数据库深分页的压力从原来的每天上百万次降到了几千次,接口平均响应时间从1.5秒降到150毫秒以内。
3.4 从方案对比看取舍
在某模拟项目里,原始查询耗时1.2秒,优化后的几种方案对比如下:
| 方案 | 实现复杂度 | 平均耗时(模拟环境) | 适用场景 |
|---|---|---|---|
| 不加优化的原始查询 | 无 | 约1200ms | 小数据量、低并发 |
| 延迟关联 | 低 | 约80ms | 保留页码跳转的传统列表 |
| 游标分页 | 中 | 稳定在50ms以内 | “加载更多”类交互 |
| 覆盖索引 + 延迟关联 | 低 | 约60ms | 大多数中小型系统 |
| 缓存介入 | 中 | 命中缓存时低于10ms | 访问热度集中、深页低频 |
没有银弹。每一条优化路径都有它的适用边界,需要结合业务形态去选择。
4. 方案选型的思考与决策清单
前面讲了一些具体手段,但在真实项目中,让人头疼的往往不是“不会写优化SQL”,而是“不知道该往哪个方向投入”。
我在实际做技术方案选型时,一般会按下面的顺序梳理出一条决策路径:
- 数据量到底有多大?是百万级、千万级,还是亿级以上。百万级很多时候几条普通索引就能给出很可观的性能改善;千万级适合用深分页优化、缓存和读写分离;亿级以上,大概率要走到分表、归档或者搜索引擎方案。
- 查询模式是什么样的?用户能不能接受“加载更多”的交互?是否需要跳页?查询条件的组合是固定的还是随意的?这决定了你适合走游标分页、延迟关联还是全文检索引擎。
- 数据库的QPS压力有多大?如果数据库本身已经接近瓶颈,做再多SQL优化也顶不住;这时优先考虑缓存层和读写分离,把读流量从主库剥离。
- 团队能接受的维护成本是多少?引入Elasticsearch或ClickHouse会带来新的运维压力,但也能换来极大的查询灵活性;分表能扛数据量,但会让所有业务查询都变得复杂。
这些年我见过太多团队一上来就分表,结果业务代码里到处都在拼表名,查询逻辑乱成一团;也见过有的系统明明数据量只有几百万,却因为一个联合索引没建对而天天出故障。技术选型要适度,不是什么数据量都要上重型武器。
注意:在做分页优化前,先做一次慢查询日志分析。很多时候慢SQL可能是缺了索引、、 统计信息过期,或者查出的数据本身就是几万条大结果集。先排除这些低垂果实,再投入重武器改造,效率会高得多。
针对常见的两类系统形态,我给出的选择倾向是:
| 系统形态 | 推荐路径 | 理由 |
|---|---|---|
| 传统后台管理系统 | 联合索引 + 延迟关联 + 深页缓存 | 需要页码跳转,改造影响小 |
| 高并发C端信息流 | 游标分页 + Redis缓存 + 必要时引入搜索引擎 | 用户接受“加载更多”,且查询条件复杂多变 |
5. 分页优化工具集:主键分页、归档表和COUNT优化
5.1 主键分页的另类写法与风险
有一种老开发者喜欢用的“奇技淫巧”:直接把上一页最后一条记录的主键当成查询条件继续往下取。
SELECT id, user_id, order_time, amount, pay_status FROM orders WHERE id > 100000 AND pay_status = 1 ORDER BY id ASC LIMIT 20;这种写法对“按主键自增顺序翻页”的场景极其高效,因为主键索引天然有序,MySQL可以直接定位到id = 100000的位置,向后取20条完事。问题是,一旦业务排序不再是主键自增顺序,这招就不灵了。如果需求是按order_time倒序,但主键ID是自增的,那么新旧数据主键顺序和业务顺序未必一致,用主键翻页就会出现明显的逻辑错误。
主键分页适合后台按创建顺序浏览、按月归档数据扫描等场景,但不能把它当成通用方案。
5.2 归档表:大数据量的终极兜底手段
很多业务数据是“热数据”和“冷数据”混杂的。三年前的订单已经完全没有查询热度,但它们依然躺在那张大表里,拖着每一查询的扫描成本。对这种数据,定期把冷数据迁移到历史归档表,是性价比非常高的优化。
比如订单系统可以保留最近6个月的数据在热表,超过6个月的按月迁移到归档表。热表体积降低,索引效率提高,深分页问题也会大幅缓解。应用层可以做一个透明路由:先查热表,热表没有再去归档表补数据。用户面对的还是同一套接口,但数据库压力已经小了一个量级。
归档表最大的坑是数据一致性。批量迁移过程中要确保不漏数据、不重复数据,建议用分批小事务迁移,并记录每一批的迁移位点,中途失败可以续跑。
5.3 别忽略COUNT(*)的成本
很多分页接口除了返回当前页数据,还会返回“总记录数”,用来渲染分页页码。这个COUNT(*)在千万级大表上往往比分页查询本身还要昂贵。对InnoDB来说,COUNT(*)需要对满足条件的行做一次完整统计扫描,无法走索引快速跳过。
通用的优化手段有几种:
- 不显示真实总数。用“返回前N页 + 是否还有更多”代替“第X页/共Y页”,前后端配合改交互。这是游标分页最常见的处理方式。
- 用估算值。如果保留页码导航,可以用
EXPLAIN里的估算行数,或者定时统计总行数缓存到Redis,接受一定的偏差。 - 用额外的计数表。业务上如果确实需要精确总数,可以维护一个计数器,实时更新,但这又引入了分布式一致性问题,只在特定场景下使用。
在这里,我推荐对绝大多数业务场景执行“能不做COUNT就不做COUNT”的原则。如果硬要做,也要做到“高频查询从缓存取,低频查询直接查库”。
6. 当SQL优化到尽头:搜索引擎与OLAP分析引擎的降维打击
6.1 为什么要“降维”处理
有些查询场景,已经不是优化SQL能解决的了。例如后台需要支持基于任意组合条件的“亿万行订单模糊搜索”,或者需要跨多个维度实时分析。这种场景如果用MySQL硬扛,不断在前端加筛选条件,索引再多也扛不住——因为条件组合无穷多,规划的索引可能根本覆盖不到某个组合。
这时候应该考虑引入搜索引擎或OLAP分析引擎,让MySQL只负责自己的主业务,逼迫复杂查询转移到专门的组件上。
一个业务典型场景可以这么设计:订单数据通过异步任务同步到专用搜索引擎,后台所有搜索、筛选、分页都由搜索引擎处理;MySQL继续承载订单创建、状态更新这类写事务和高频主键查询。搜索接口的响应时间在百毫秒以内,数据量涨到数亿也不怕。
这里多加一句:切忌把搜索引擎当作万能组件。它的数据一致性相对弱,写入延迟大,运维复杂度也高。只要原有的几百毫秒分页可以接受,就没必要为“听起来更酷”买单。
6.2 两类常用引擎选型对比
| 引擎 | 擅长的场景 | 不适合的场景 |
|---|---|---|
| Elasticsearch | 全文搜索、复杂组合筛选、聚合分析、海量日志 | 强事务、超高频点查 |
| ClickHouse | 海量数据分析、实时聚合、大数据量扫描 | 点查、频繁更新 |
我个人的选择习惯是:以检索为主、需要毫秒级响应就选Elasticsearch;以分析为主、需要批量扫描和聚合就选ClickHouse。两者也经常协同使用,只是一般情况下没有那个必要。
6.3 一个虚拟业务的落地复盘
用一个模拟项目来说明。某跨平台订单中心后台,原始订单数据保存在MySQL中,超过三千万行。客服需要按订单号、手机号、订单状态、时间段等条件组合查询,经常翻到几十页以后。
当时的改造路径比较明确:
- MySQL侧先做基础索引优化,把
(pay_status, order_time, id)联合索引加上, - 前端交互从“页码跳转”改为“加载更多”,后端改为游标分页配合状态字段排序,
- 高频热页数据加Redis缓存,深页数据保留缓存兜底。
- 后续为了支持公式化复杂筛选,又引入了搜索引擎,把后台筛选查询全部迁出MySQL。
结果之后,客服后台查询从平均2秒降到150毫秒以内,数据库慢查询量从每天几百条降到几乎清零。这套改造只花了两周时间,效果却持续稳定了很长时间。
这种循序渐进、按需引入的改造步骤,是我最推荐的做法。一上来就上Elasticsearch,未必能解决SQL层面的基础健康问题,反而会把系统复杂度带上去。
7. 常见问题与排查技巧实录
优化做多了,总会碰到各种奇怪的问题。我把几个高频问题整理成速查表,方便一线开发排查。
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| 索引加了却不生效 | 查询条件写法不匹配索引最左前缀 | 检查WHERE条件是否包含联合索引的第一列 |
| 分页接口变慢但SQL单跑很快 | 接口代码里多了循环查询、序列化开销大 | 开启慢查询日志,把真实接口SQL抓出来对比执行计划 |
| 游标分页出现重复/缺失数据 | 排序字段不唯一,或排序字段顺序与索引不匹配 | 检查排序字段是否包含唯一主键,必要时改为复合排序 |
| 翻到很靠后的页时仍然慢 | 延迟关联没能完全消除深分页扫描成本 | 考虑切换游标分页,或增加深页缓存 |
COUNT(*)和大分页组合导致接口超时 | 两者相加的代价太高 | 去掉精确COUNT,改用估算值或“是否还有更多” |
| 分页排序字段类型不一致导致索引失效 | 字段隐式转换,或字符集排序规则不一致 | 保持查询字段和索引字段类型、排序规则完全一致 |
排查这类问题时,我最常用的工具组合是EXPLAIN ANALYZE(MySQL 8.0+)+performance_schema。先用EXPLAIN看执行计划,确认是否走了索引、有没有Using filesort;再用EXPLAIN ANALYZE看实际执行时间占比,定位耗时最大的阶段。不要让慢查询停留在“感觉慢”的层面,直接用数据说话。
7.1 典型问题一:为什么索引建了还是不生效
有一次某同学在业务群反馈:订单表上建了(pay_status, order_time)联合索引,可查询还是走全表扫描。我把他的SQL拿过来一看,发现他是这样写的:
SELECT * FROM orders WHERE pay_status IN (0, 1, 2) ORDER BY order_time DESC LIMIT 20;这里的问题在于,IN (0, 1, 2)几乎等于无过滤条件,索引的第一列区分度极低,MySQL优化器发现扫描整个联合索引比回表还贵,干脆选择全表扫描。解决方式是:pay_status的过滤条件不要包含接近全量的值,或者是把order_time作为联合索引第一列,把pay_status放后面。这提醒我们,在设计联合索引时,优先考虑过滤性强的等值条件。
7.2 典型问题二:按时间范围分页为什么还需要额外加索引
还有一位同学说他自己用了游标分页,每天都按order_time翻订单,可查询到第5页就几秒钟。我看了一下他的表结构,order_time上是有普通索引的,但查询条件是:
WHERE user_id = 12345 AND order_time < '2024-06-01 12:00:00' ORDER BY order_time DESC LIMIT 20;单独在order_time建索引,不代表MySQL能利用它完成定位。这里user_id = 12345是过滤条件,order_time < ...是范围条件。MySQL要同时满足这两点,最好的结构是(user_id, order_time)复合索引。如果只有order_time单列索引,MySQL必须先按索引扫描所有小于这个时间的记录,再在内存中过滤user_id,过程中数据量一大自然就慢。这也是深分页场景中最容易踩的坑——“有索引”不等于“索引有效”。
7.3 典型问题三:深分页时LIMIT偏移量很大但总是很快
有时候深分页查询并不慢,这是因为条件本身很有过滤性,比如user_id = 12345的订单量只有几百条,翻到第100页也就几千条,自然快。要准确判断慢不慢,不能单看页码数字,更要看“偏移量对应的扫描量”。
如果页面序号已经到几千页,但过滤条件仍然精确到某一个用户或某一种状态,那扫描量其实并不大。真正会出问题的是“全量条件下的深度分页”,例如不带用户过滤条件、全订单列表按时间排序拉到很后面几页。
7.4 典型问题四:数据膨胀后索引失效需要重建
数据量上来后,某些老索引会失效或变得低效。最常见的情况是:当初建索引时数据量小,区分度足够;后续数据膨胀,某个字段的区分度下降(比如pay_status字段0和1占了99%),导致优化器判定走索引还不如全表扫描。
遇到这种情况,不能只靠优化SQL。把慢查询日志中执行计划不稳定、时而走索引时而不走索引的SQL挑出来,分析字段区分度变化,考虑拆索引或重新设计查询条件。有些表,每隔一段时间做一次索引重建和统计信息更新,是有必要的。
8. 沿着这些思路往前走一步
多年和分页查询打交道,我个人最深的体会是:分页优化的本质,不是想方设法让MySQL执行得更快,而是想办法让它少干活。延迟关联减少回表次数,游标分页减少扫描行数,缓存减少数据库访问次数,归档表减少扫描总体积——每一条路径的底层逻辑都是“减少无效工作”。
如果你正在做一个新系统,一开始就要把分页交互设计好。能接受“加载更多”就用游标分页,从一开始就别引入页码跳转;必须保留页码导航的,预留深页查询的缓存策略和索引设计。很多团队是在业务已经跑了一年、数据量翻了20倍之后才开始优化,到那时,每一次试错都要用线上故障来买单。
我始终认为,SQL优化能力是一线开发者的基本功,即便搜索引擎和分布式架构再流行,MySQL依然是绝大多数中小企业的数据底座。你把上面的这些方案亲手在本地环境测一遍,把执行计划、耗时数据记录下来,形成自己的调优手记,远比看十篇文章有用。
做一个能扛住亿级数据的分页接口,是一件能获得极大满足感的事情。尤其是看到原来三秒才出的页面变成毫秒级响应时,你会觉得这一切折腾都值得。