SQL Server分页性能优化:从Row_Number到键集分页的实战解析

发布时间:2026/10/6 13:31:49
SQL Server分页性能优化:从Row_Number到键集分页的实战解析 前几天排查一个线上慢查询发现罪魁祸首居然是一条看起来很普通的分页 SQL。两张表关联查询数据量不过百万级用 Row_Number() 分页翻到后半段时接口响应直接飙到 8 秒多数据库 CPU 被打到 70% 以上。这让我重新审视了一遍 SQL Server 的分页优化问题也把 Row_Number() 分页存在的几个坑彻底理清楚了。这篇文章就围绕这次优化过程展开既讲原理也给出可落地的替代方案希望给正在被分页性能折磨的朋友一些参考。1. 问题现场一次慢查询引发的分页反思1.1 业务背景与表象这是一个典型的后台管理系统列表页主表存放订单子表存放订单明细前端按创建时间倒序分页展示。初期数据量只有十几万的时候Row_Number() 分页跑得飞快基本感觉不到延迟。后来业务推广数据量涨到接近两百万问题就暴露了翻到第 50 页之后页面加载明显变慢再往后翻基本要等八九秒才能出数据。起初我以为是数据库服务器负载过高看了监控才发现 CPU 和内存都正常但这条分页 SQL 的逻辑读非常高单次执行达到十几万次。问题定位到 SQL 本身而不是硬件资源不够。这其实很典型——分页性能的瓶颈通常不是机器而是写法和索引之间的匹配度出了问题。1.2 最初的 Row_Number() 分页写法当时的 SQL 长这样WITH OrderPage AS ( SELECT ROW_NUMBER() OVER (ORDER BY o.CreateTime DESC) AS RowNum, o.OrderId, o.OrderNo, o.CustomerName, o.CreateTime, d.DetailInfo FROM dbo.Orders o INNER JOIN dbo.OrderDetails d ON o.OrderId d.OrderId ) SELECT * FROM OrderPage WHERE RowNum BETWEEN 10001 AND 10020 ORDER BY RowNum;这段 SQL 在逻辑上没有任何问题先给结果集编号然后取第 10001 到 10020 条。但问题恰恰出在先编号这一步——它要把满足条件的所有数据都排好序、编好号然后才去截取这 20 条。数据量小的时候无所谓数据量一大前面的排序和编号操作就变成了巨大的浪费。1.3 性能瓶颈初现我用 SET STATISTICS IO ON 和 SET STATISTICS TIME ON 实测了一下这一条查询的逻辑读接近 18 万CPU 耗时 6.8 秒。执行计划里出现了两个明显的代价点排序操作占了 47%表连接操作占了 31%。更关键的是整个执行计划显示优化器为了算 Row_Number()必须把两张表关联后的所有行全部物化排序即使最终只取 20 行也逃不掉全量计算的命运。我尝试给 Orders 表的 CreateTime 建了索引但效果有限。原因在于Inner Join 加上排序键不在驱动表上索引无法直接满足排序和筛选的双重需求。这就是 Row_Number() 分页的第一个隐患——它对执行计划的要求很高一旦排序键和过滤条件无法完全匹配索引优化器就可能选择显式排序性能瞬间崩塌。2. Row_Number() 分页为什么慢核心原理拆解2.1 Row_Number() 分页的执行逻辑Row_Number() 是 SQL Server 2005 引入的窗口函数它的作用是在结果集内按指定顺序生成行号。分页时我们通常这样用外层查询通过 RowNum BETWEEN 条件截取目标区间的数据。问题在于Row_Number() 是在结果集生成之后才进行计算的。什么意思呢SQL Server 需要先执行 FROM、JOIN、WHERE 这些子句得到完整的结果集然后根据 ORDER BY 子句对结果集排序排序完成后才逐行赋予行号最后外层 WHERE 过滤掉不需要的行。这个过程里你翻到第 100 页和翻到第 1 页工作量几乎一样。第 1 页虽然只需要前 20 行但仍然要把所有行排序、编号然后从 100 万行里取前 20 行。唯一的差别可能在 TOP 优化上——如果你用 TOP (20) 配合 ORDER BY优化器可能提前终止排序但 Row_Number() 的写法通常是一个 CTE 或子查询外层再过滤优化器一般不会把这个逻辑下推导致全量排序不可避免。2.2 大偏移量下的无用功可以这样理解Row_Number() 分页就像一本没有目录、没有书签的纸质书。你想看第 100 页的内容只能从第 1 页开始一页一页翻过去翻到第 100 页才能停。虽然最终你只看了第 100 页那一页的内容但前面 99 页你虽然没有细看却必须做翻页这个动作。在数据库层面那个翻页动作就是对前 10000 行数据完成排序和编号。更糟糕的是排序不是免费的尤其是指定 CreateTime DESC 这种非聚集索引排序键时排序操作可能还要回表查询其他字段。每多翻一页工作量线性增长最终导致接口延迟越来越高。2.3 排序不稳定带来的隐蔽 Bug这是 Row_Number() 分页最容易被忽视的问题——如果 ORDER BY 的字段不是唯一键那么数据分布不均匀时分页结果可能出现重复或缺失。举个例子列表按 CreateTime 倒序排列同一秒内可能插入多条数据。ROW_NUMBER() 在排序时如果遇到 CreateTime 相等的行SQL Server 会按物理顺序随便给个先后。当你执行第一页查询时某条记录可能排在第 19 位再执行第二页查询时由于数据变化或执行计划差异同样的记录可能变成第 21 位。于是你会在第一页看到它第二页也看到它或者两页都看不到它。这种问题在测试环境很难复现因为数据量小、并发低、物理顺序稳定。但生产环境数据持续写入加上并行执行计划可能造成不同的数据分布翻页到后面时很容易出现重复记录反馈。用户可能不一定会立刻发现但一旦被提 bug排查起来非常痛苦。2.4 索引使用上的陷阱有人会说那我给排序键建个索引不就行了实际上对于 Row_Number() 分页这里有个常见误区给 CreateTime 建了索引但结果集需要返回 * 或大量列而索引里没有这些列SQL Server 必须对每一行执行 Bookmark Lookup键查找。当筛选范围很大时键查找次数也会很多整体性能反而比全表扫描更差。另外如果分页 SQL 里带了 JOIN驱动顺序也会影响索引选择。订单表按 CreateTime 建了索引优化器可以选择先扫订单表、再去关联明细表但如果优化器统计信息不够新它可能会先扫描明细表再回订单表查找此时 CreateTime 索引根本用不上。这个问题我在排查时遇到过多次最后都是靠更新统计信息或者加查询提示解决的。3. 优化方案一OFFSET ... FETCH 的改进与局限3.1 从 Row_Number 切换到 OFFSET ... FETCHSQL Server 2012 引入了 OFFSET ... FETCH这是标准 SQL 的分页语法。把原来的 CTE 写法改成这样SELECT o.OrderId, o.OrderNo, o.CustomerName, o.CreateTime, d.DetailInfo FROM dbo.Orders o INNER JOIN dbo.OrderDetails d ON o.OrderId d.OrderId ORDER BY o.CreateTime DESC OFFSET 10000 ROWS FETCH NEXT 20 ROWS ONLY;相比 Row_Number()这种写法更简洁执行计划里不再有专门的 ROW_NUMBER 计算算子而是通过表 Spool 或直接定位到偏移位置后读取目标行。在某些场景下OFFSET ... FETCH 能更好地利用索引顺序避免显式排序。比如 ORDER BY 字段正好是索引键优化器可以直接从索引的指定位置开始扫描扫描到目标行数即停止。我做了个简单对比同样的表和数据OFFSET ... FETCH 翻第 501 页时逻辑读从 18 万降到了 9 万耗时从 6.8 秒降到了 3.2 秒。性能提升明显但代价仍然不小。3.2 为什么 OFFSET 也不够完美很多人以为把 Row_Number() 换成 OFFSET ... FETCH 就万事大吉实际不然。OFFSET ... FETCH 的问题在于它仍然需要先跳过 10000 行而跳过这些行的过程中SQL Server 依然会读取它们的索引条目或堆数据。换句话说OFFSET 10000 并不是直接跳过去而是数过去。虽然不需要把所有行的所有列都取出来但索引扫描仍然要从第一行开始一行一行地往后数数到 10000 后再取 20 行。这个数行数的操作在大偏移下仍会消耗大量 IO。在 SQL Server 2012 之后的版本里如果 ORDER BY 字段有合适的索引OFFSET ... FETCH 的执行计划会显示一个Index Scan Top N Sort或Index Seek Key Lookup的组合但本质上索引扫描还是要遍历偏移量之前的所有索引行。数据量越大、偏移量越大耗时依然线性增长。3.3 实测对比Row_Number vs OFFSET我把两种写法在 100 万行数据下做了多轮测试结果如下表数据为本地环境实测单位毫秒分页位置Row_Number() 耗时OFFSET ... FETCH 耗时逻辑读Row_Number逻辑读OFFSET第 1 页前 20 行180352100180第 50 页995-1015行42019081003400第 500 页9980-10000行6800320018200091000第 5000 页99800-99820行超出了测试耐心26800跑不完852000可以看到OFFSET ... FETCH 虽然在每一档都比 Row_Number() 快一倍左右但依然存在大偏移量导致的性能衰减。翻到第 5000 页时2.68 秒的延迟仍然无法接受。这时候我才意识到要根治分页性能问题不能只在取数方式上打补丁而是要改变分页模型本身。4. 优化方案二键集分页Keyset/Seek Method的真正落地4.1 键集分页的核心思想所谓键集分页英文叫 Keyset Pagination 或 Seek Method核心思想一句话不要告诉数据库我要跳过多少行而是告诉它我要从哪一行之后开始取。传统分页的语义是给我第 N 页键集分页的语义是给我上次看到的那条记录之后的 20 条。这样数据库可以利用索引直接定位到目标位置避免扫描之前所有的行。整个过程跟翻书完全不同更像查字典——你知道某个字在哪个页码附近直接翻过去而不是从第一页开始数。对应到 SQL 写法你要把上一页最后一条记录的排序键值作为查询条件传进来。比如上一页最后一条记录的 CreateTime 是 2024-06-15 14:30:20OrderId 是 12345那么下一页查询就可以写成SELECT TOP (20) o.OrderId, o.OrderNo, o.CustomerName, o.CreateTime, d.DetailInfo FROM dbo.Orders o INNER JOIN dbo.OrderDetails d ON o.OrderId d.OrderId WHERE (o.CreateTime 2024-06-15 14:30:20) OR (o.CreateTime 2024-06-15 14:30:20 AND o.OrderId 12345) ORDER BY o.CreateTime DESC, o.OrderId DESC;注意这里 WHERE 条件的写法不是为了简单过滤而是为了构造一个符合排序键顺序的 SARG 条件让 SQL Server 能够用上索引 Seek直接定位到目标位置往下读 20 行。这样查询成本永远不会随着页码增加而增加永远只扫描 20 行左右。4.2 具体 SQL 写法与参数化实际开发中我们不会在 SQL 里硬编码值而是使用参数。上面的写法可以改造成如下形式DECLARE LastCreateTime DATETIME 2024-06-15 14:30:20; DECLARE LastOrderId INT 12345; SELECT TOP (20) o.OrderId, o.OrderNo, o.CustomerName, o.CreateTime, d.DetailInfo FROM dbo.Orders o INNER JOIN dbo.OrderDetails d ON o.OrderId d.OrderId WHERE o.CreateTime LastCreateTime OR (o.CreateTime LastCreateTime AND o.OrderId LastOrderId) ORDER BY o.CreateTime DESC, o.OrderId DESC;这段 SQL 对索引的要求是需要在 Orders 表上建立 (CreateTime, OrderId) 的复合索引。为什么必须有 OrderId 作为第二键因为 CreateTime 可能重复需要唯一键来保证排序的确定性。如果 OrderId 是主键且是递增的那它天然适合作为第二排序键。这里有个细节需要注意如果你用了 OR 条件优化器有可能把 OR 展开成两个分支再合并这仍然可以走索引但更推荐的写法是用 UNION ALL 明确区分两个分支SELECT TOP (20) o.OrderId, o.OrderNo, o.CustomerName, o.CreateTime, d.DetailInfo FROM dbo.Orders o INNER JOIN dbo.OrderDetails d ON o.OrderId d.OrderId WHERE o.CreateTime LastCreateTime UNION ALL SELECT TOP (20) o.OrderId, o.OrderNo, o.CustomerName, o.CreateTime, d.DetailInfo FROM dbo.Orders o INNER JOIN dbo.OrderDetails d ON o.OrderId d.OrderId WHERE o.CreateTime LastCreateTime AND o.OrderId LastOrderId ORDER BY CreateTime DESC, OrderId DESC;但这样会多出一次连接和排序实际测试下来如果复合索引设计合理OR 写法反而能生成更紧凑的执行计划。这一点我建议你根据实际执行计划来选择而不是盲目迷信网上教程。我最终采用了 OR 写法配合 OPTION (RECOMPILE) 避免参数嗅探实测执行计划里看到了清晰的 Index Seek。4.3 索引设计与排序键的选取键集分页对索引的依赖比传统分页高得多。没有正确的索引键集分页的性能不一定比 OFFSET 好甚至可能更差。我的经验是排序键必须满足以下几个条件第一排序键至少包含一个唯一列。主键是最直接的选择如果主键是自增列那么业务时间字段加主键的组合基本够用。第二排序键的顺序必须与 ORDER BY 完全一致包括 DESC/ASC。第三WHERE 条件里的列必须出现在索引中并且最好是索引的前导列。比如上面那个例子索引应该建在 Orders 表上CREATE NONCLUSTERED INDEX IX_Orders_CreateTime_OrderId ON dbo.Orders (CreateTime DESC, OrderId DESC);建立这个索引后执行计划会非常漂亮索引 Seek 定位到 (CreateTime, OrderId) 小于参数值的第一个位置然后顺序读取 20 行每行回到聚集索引取其他字段。由于只读 20 行键查找次数也就是 20 次左右逻辑读从几万瞬间降到几十。不过要注意创建 DESC 还是 ASC取决于你的排序方向。如果列表经常用正序排那就建 ASC如果经常倒序排就建 DESC。SQL Server 的索引键方向对性能的影响不容忽视方向不一致时优化器可能放弃 Seek改用反向扫描反向扫描的效率通常不如正向扫描。4.4 与前端交互的接口设计键集分页没法直接兼容传统翻页的页码 UI因为它不提供跳到第 5 页的能力。所以接口层面需要调整。常见做法有两种。一种是上一页/下一页模式后端返回当前页最后一条记录的排序键值前端把它作为参数随下一次请求带上。这种模式最经典也最容易被接受。另一种是无限滚动模式移动端不断下拉加载后端每次返回下一页数据同时返回 nextKey前端拿到 nextKey 作为下次请求的游标。这两种模式的本质一样都是把位置交给业务缓存而不是每次去数据库计算。如果你的产品经理坚持要做页码跳转那键集分页就无能为力了。这种情况我建议用混合方案前几页用 OFFSET ... FETCH 支持页码跳转数据量超过一定阈值后自动切到键集分页的上一页/下一页模式。甚至可以在页码控件上做文章比如只显示最近几页和位置指示符跳转按钮实际上只是滚动加载的变种。5. 实操记录与踩坑实录5.1 一个容易被忽略的排序字段陷阱我在改造时遇到了一个特别隐蔽的问题业务需求里要求的排序顺序是CreateTime 倒序但 CreateTime 是 DATETIME 类型精度只到 3 毫秒。在高并发下同一批次导入的数据可能在毫秒级内有大量重复值。单靠 CreateTime 排序哪怕加了 OrderId 作为第二键也还是会出现问题——因为 OrderId 是自增主键但它只记录插入顺序如果某些历史数据是通过 ETL 工具批量导入的OrderId 的顺序可能与 CreateTime 的顺序不一致。举个例子A 记录 CreateTime 为 12:00:00.100OrderId 为 1000B 记录 CreateTime 为 12:00:00.100OrderId 为 1001。按 CreateTime DESC, OrderId DESC 排序后B 在 A 前面。这个顺序在数据导入后是固定的但如果后续有补数操作给 A 的 CreateTime 改成了 12:00:00.200那这两条的相对顺序就变了。这种变化如果发生在用户翻页过程中就可能造成重复或遗漏。解决办法是把排序键从业务时间字段完全切换到一个不可变且唯一的字段上。比如使用 OrderId 作为唯一的排序键不要跟着 CreateTime 走。如果业务上必须按创建时间倒序那就要保证 CreateTime 的写入顺序和自增序列一致并且不允许业务修改 CreateTime。我们把表结构从可修改 CreateTime调整成了通过触发器禁止更新 CreateTime才算彻底稳定了排序。5.2 当数据发生变更时键集分页如何处理键集分页的另一个常见疑虑是如果上一页最后一条记录在用户浏览期间被删除了下一页查询把它作为条件时怎么办答案很直接如果是删除查询条件会找不到对应的 CreateTime 和 OrderId 组合但 SQL Server 不会报错。它会在索引里寻找下一个符合条件的记录并继续返回相当于自动跳过被删除的那条。如果你拿到的游标是上一页最后一条的完整排序键那么即使这一条刚被删除下一页查询依然可以正常定位因为查询条件是小于不是等于。如果是更新问题会复杂一些。比如上一页最后一条记录的 CreateTime 被更新了导致它排到了别的页。这时下一页查询携带的旧 CreateTime 可能找不到任何记录或者位置偏移。解决思路是尽量用不可变字段作为排序键避免更新导致游标失效。如果排序键是 OrderId 这种自增且永不更新的字段就根本不用担心这个问题。我在项目里的做法是将 OrderId 作为游标主键CreateTime 只作为展示字段查询排序也只用 OrderId DESC。虽然业务上看起来最新创建的订单排在前面变成了最新插入的订单排在前面但由于订单创建的基本都是即时插入这个偏差不大。如果遇到历史数据迁移导致 OrderId 顺序与实际创建时间不一致那就需要单独维护一个业务序号字段比如 Version 或 Sequence保证它的逻辑顺序和业务顺序一致。5.3 单条查询优化到毫秒级后的整体效果改完键集分页后我重新跑了之前那个慢查询。同样的位置翻到第 5000 页SQL 耗时稳定在 30 毫秒左右逻辑读只有 48 次。这个效果可以说是质的变化而且页码越深优势越大。更重要的是这个优化不仅拯救了分页接口还降低了整个数据库的负载。原来高峰期数据库 CPU 达到 70%大量 IO 被分页查询拖垮优化后同样的业务流量下CPU 峰值只有 20% 出头。后台管理页面的响应时间从 8 秒降到了 200 毫秒以内产品经理都忍不住来问做了什么优化。6. 不同场景下的分页选型建议6.1 数据量小或管理后台等场景如果你的表数据量在十万以下或者用户规模不大Row_Number() 分页和 OFFSET ... FETCH 的差距完全可以忽略。毕竟一百行数据的排序几乎不耗时间用键集分页反而会增加开发复杂度。这种情况我不建议你强行改造保持原有代码风格就好。管理后台这类场景通常有页码跳转需求产品不可能快速改成游标模式。那就在现有写法基础上做点简单优化比如给排序键建立覆盖索引、减少返回列、用 TOP ORDER BY 代替 Row_Number()。实测下来覆盖索引配合 OFFSET ... FETCH在十万级数据量下翻到最后一页也基本能控制在 100 毫秒以内足够用了。6.2 大数据量或高并发互联网场景数据量超过百万、并发量高、用户频繁翻页的场景我强烈建议直接用键集分页。尤其是 C 端列表、信息流、日志查询这些场景用户根本不需要精确页码只需要加载更多按钮或者无限滚动。这类需求与键集分页天然契合。在设计时一定要把游标字段的索引设计好。索引如果没建对键集分页可能在第一次查询就比 OFFSET 还慢。比如前面那个例子索引必须是 (CreateTime DESC, OrderId DESC)而不是单列索引。另外游标字段最好返回给前端之后做一次 URL 编码防止特殊字符导致参数传递出错。6.3 移动端或无限滚动场景移动端无限滚动是键集分页最典型的应用场景。每次滚动到底部客户端拿到下一页的游标然后请求后续数据。这里有一个容易踩的坑客户端的滚动加载请求如果用 GET 方式游标会暴露在 URL 里可能被日志记录。所以如果有敏感信息建议用 POST 请求或者对游标做签名校验。还有一个细节无限滚动加载时用户会快速连续触发多次请求。如果服务端不一致可能导致数据跳过或重复。我的习惯是在接口层加一个游标防重机制如果收到的游标比上一车返回的游标还旧就直接返回空数据不做数据库查询减轻无用压力。6.4 使用 ORM 框架时怎么落地很多朋友用的是 EF Core、MyBatis-Plus 这类 ORM 框架它们默认提供的分页方法都是基于 Skip/Take 或 Limit/Offset 的。以 MyBatis-Plus 为例它的 Page 对象底层用的是 Row_Number() 或者 LIMIT 偏移量数据量一大一样会遇到性能问题。如果你想在 ORM 里落地键集分页不要期望框架直接支持通常需要自己写 SQL。MyBatis 里可以用 XML 自定义查询EF Core 可以通过 FromSqlRaw 写原生查询。我建议把游标分页封装成一个公共组件输入上一页游标和页大小输出结果和下一页游标。这样虽然失去了 ORM 的强类型便利但性能收益是实打实的。如果实在要在 EF Core 里用键集分页可以借助 Z.EntityFramework.Plus 或手工构建 Where 子句。不过这些第三方库不一定支持所有 SQL Server 版本生产环境还是得先做兼容性测试。7. 常见问题排查速查表7.1 Row_Number() 分页结果重复或缺失先检查排序列是否唯一。如果 ORDER BY 的字段没有唯一性约束就在 ORDER BY 末尾加上主键保证排序稳定。其次检查外层查询是否有 JOIN多张表 JOIN 后如果排序列来自被驱动表可能会导致每行编号的物理顺序不固定。第三看看查询执行计划里有没有并行运算符并行扫描可能打乱输出顺序。可以加 OPTION (MAXDOP 1) 临时验证如果问题消失说明并行导致的顺序不稳定。7.2 使用 OFFSET ... FETCH 后无法与旧版本兼容OFFSET ... FETCH 是 SQL Server 2012 才引入的语法。如果你要兼容 SQL Server 2008 R2 及更早版本就只能用 Row_Number()。另外OFFSET ... FETCH 不能直接在视图或派生表中使用 ORDER BY这一点和 Row_Number() 不同容易踩到语法错误。遇到这种情况可以在子查询里先用 Row_Number() 生成序号再在外面用 BETWEEN 过滤或者升级数据库版本。7.3 参数嗅探导致分页查询不稳定这个问题在键集分页里也会遇到。SQL Server 会缓存第一次执行时的执行计划如果第一次传入的游标值选择性很好之后传入一个选择性很差的值可能仍然沿用第一次的计划导致性能下降。我的做法是在键集分页的查询里加上 OPTION (RECOMPILE)。虽然这会让每次查询重新编译带来少量 CPU 开销但对于分页这种每次参数都不同的查询来说性价比非常高。另外如果游标字段是 DATETIME 类型传入的值如果是字符串可能需要显式转换。注意转换的方式最好使用参数而不是直接拼字符串否则容易造成隐式转换让索引失效。我在一个项目里发现游标查询很慢检查执行计划后发现 CreateTime 字段出现了 Convert 算子就是因为前端传过来的是字符串SQL 里直接 LastCreateTime 2024-06-15 14:30:20导致 CreateTime 索引无法 Seek。改成参数化查询后性能立刻恢复。7.4 非分页缓冲池占用过高怎么办虽然这个热搜词和分页优化本身关系不大但很多朋友在排查分页性能时可能会同时发现内存计数器异常。如果出现非分页缓冲池Non-Paged Pool占用很高通常与驱动、网卡或某些第三方软件有关。SQL Server 本身并不是非分页缓冲池的主要消费者。这时候可以先通过任务管理器或池监控工具看看哪个进程的 NP Pool 持续增长再逐步排查。不要急着去调整 SQL Server 内存配置那是南辕北辙的做法。7.5 分页 SQL 在宝塔环境中无法识别有些朋友用宝塔面板部署 SQL Server安装后面板识别不了数据库。这通常是因为宝塔的数据库管理插件没有正确注册 SQL Server 服务。解决思路是确保 SQL Server 服务已启动并且 TCP/IP 协议已启用在 SQL Server 配置管理器中把监听端口设为 1433然后在宝塔中添加数据库时不要用默认的 localhost而是填 127.0.0.1。如果还不行检查防火墙是否放行 1433 端口。这个问题本质上与分页优化无关但出现频率很高顺手记录一下。8. 写在最后一点个人经验这次优化让我切实体会到分页从来不是一个孤立的功能点它牵扯到索引设计、排序稳定性、接口语义甚至产品交互。Row_Number() 不是不能用但它更适合那些不需要深层翻页、数据量可控的管理类页面。真正面向海量数据的分页键集分页才是终极方案。如果你的系统已经跑了好几年大量分页代码都是 Row_Number() 写法我建议你不要一次性全改而是挑一个数据量最大、性能瓶颈最明显的接口做试点。先用键集分页把下一页接口做出来保留原有页码接口观察线上效果。实测稳定后再逐步推广。我这次就是这么做的前后花了三个工作日收益却覆盖了后续一整年的分页类问题工单。最后再分享一个小技巧如果你不方便改前端交互但又要解决深层分页性能问题可以在数据库层做一个伪键集方案——把页码转换成游标。比如每页的 lastId 都缓存进 Redis页码请求时先从 Redis 拿到 lastId再走键集分页 SQL。这样既能保留页码输入的方式又能享受键集分页的性能优势。代价是要缓存好每页的游标并且数据变动时要及时更新缓存。这个方案我实际验证过对于后台管理系统是个不错的折中做法。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询