新闻详情

新闻详情

首页 / 资讯中心 / 详情

LIMIT 深分页从 200 万行跳到 80 万行:MySQL 8.0 延迟关联改写前后对比

发布时间:2026/9/30 2:45:35来源:尧图网络
LIMIT 深分页从 200 万行跳到 80 万行:MySQL 8.0 延迟关联改写前后对比
本文摘要深分页翻到 80 万行耗时涨到秒级EXPLAIN显示命中索引。延迟关联内层只取主键、外层回表把 80 万次回表降到 20 次。仅当排序列被索引覆盖且带唯一决胜列时有效按主键排序反而更慢。一、问题与结论订单列表接口GET /orders?page40001size20最终生成SELECT ... FROM orders ORDER BY create_time DESC, id DESC LIMIT 800000, 20前几页几十毫秒跳到深页后进入秒级慢日志里rows_examined与offset N同量级而EXPLAIN写着keyidx_ct_id、Extra: Using index看上去索引全都用上了。标题数字口径先说清楚表orders约 200 万行“跳到 80 万行”指offset 800000、每页 20 行描述的是翻页位置不是任何“扫描行数下降”的口径。下文计数与耗时均标注为待实测。结论三条慢的主因不是索引没命中而是二级索引扫过offset N条条目后还要为每行回表取amount、note这类非索引列。延迟关联把回表次数从offset N降到N索引条目遍历量不变。只有排序列被窄索引覆盖、且ORDER BY带唯一决胜列时才有效排序键是主键时改写反而多一次 join。二、排查与选择依据先定度量口径再谈优化。可对比的数字有三个EXPLAIN ANALYZE的实际行数与耗时、Handler_read_next/Handler_read_key计数、慢日志的rows_examined。三者分别是计划树上的真实循环次数、索引顺序扫描与按键读取次数、执行器检查过的行。改写前后必须用同一口径比较只贴耗时无法区分“省了回表”与“buffer pool 变热”。“联合索引建成却只命中一列”常见三种成因最左前缀WHERE user_id ?用不上idx_ct_id (create_time, id)因为create_time不在条件里。范围列截断WHERE create_time ?是范围条件时id不再参与排序ORDER BY id DESC只能走Using filesort。覆盖列判断InnoDB 二级索引条目隐含主键列内层只取id时KEY (create_time)也够要取user_id就必须回表。判断依据是EXPLAIN的Extra: Using index与key_len不是索引名。替代方案与取舍方案选择条件代价边界延迟关联必须跳页、排序列有窄索引、行宽大或冷数据需要索引SQL 双份维护排序列为主键时无收益不解决COUNT(*)键集分页WHERE (create_time, id) (?, ?)只做上一页/下一页或无限滚动接口需改造、必须有唯一决胜列无法跳到任意页反向扫描LIMIT total-offset-N, N深页多为末尾且 UI 已拿到总数依赖总数准确性中段深页依旧慢产品侧无限滚动或虚拟列表交互可以改需产品与前端配合导出、“跳到第 N 页”仍需兜底不该用延迟关联的场景ORDER BY就是id且取列都在聚簇索引里offset恒小于几千表很小或结果集已被过滤到几十行列表页仍用SQL_CALC_FOUND_ROWS拿总数该特性在 8.0 较新小版本已标记弃用具体版本以本机手册为准。三、关键原理LIMIT offset, N的成本由两部分构成扫过offset N条索引条目再丢弃offset条。只有当ORDER BY顺序与索引一致、且取列被该索引覆盖时才不发生回表一旦要取note这类列被检查的行就要按主键回聚簇索引读一次。延迟关联是内层子查询只取id在窄索引上完成ORDER BY与LIMIT外层再按主键取回 20 行完整记录。省掉的是回表不是索引条目遍历pad CHAR(200)这类宽行会放大收益窄行收益有限。两个正确性要求ORDER BY必须带唯一决胜列id否则排序键重复会造成跨页重复或漏行这是正确性问题而非性能问题改写为 join 后外层必须重复ORDER BYjoin 不保证输出顺序。计划形态还会受optimizer_switch中derived_merge与派生表条件下推影响升级版本或数据分布变化后要重看EXPLAIN。四、可运行示例环境MySQL 8.0 InnoDB先用SELECT VERSION();与SHOW VARIABLES LIKE cte_max_recursion_depth;确认递归造数需临时调大会话变量EXPLAIN ANALYZE需较新 8.0.x以本机版本手册为准。DROPDATABASEIFEXISTSpagedemo;CREATEDATABASEpagedemoDEFAULTCHARACTERSETutf8mb4;USEpagedemo;CREATETABLEorders(idBIGINTUNSIGNEDNOTNULLAUTO_INCREMENT,create_timeDATETIME(3)NOTNULL,user_idBIGINTUNSIGNEDNOTNULL,statusTINYINTNOTNULL,amountDECIMAL(12,2)NOTNULL,noteVARCHAR(255)NOTNULLDEFAULT,padCHAR(200)NOTNULLDEFAULT,PRIMARYKEY(id),KEYidx_ct_id(create_time,id))ENGINEInnoDB;SETSESSIONcte_max_recursion_depth2100000;INSERTINTOorders(create_time,user_id,status,amount,note)WITHRECURSIVE seqAS(SELECT1ASnUNIONALLSELECTn1FROMseqWHEREn2000000)SELECTTIMESTAMPADD(MILLISECOND,n MOD86400000,2024-01-01 00:00:00),1000(n MOD100000),n MOD5,ROUND(1(n MOD9999)/100,2),CONCAT(order-,n)FROMseq;ANALYZETABLEorders;SELECTCOUNT(*)FROMorders;改写前EXPLAINANALYZESELECTid,create_time,user_id,status,amount,noteFROMordersORDERBYcreate_timeDESC,idDESCLIMIT800000,20\G改写后延迟关联EXPLAINANALYZESELECTo.id,o.create_time,o.user_id,o.status,o.amount,o.noteFROMorders oJOIN(SELECTidFROMordersORDERBYcreate_timeDESC,idDESCLIMIT800000,20)tONo.idt.idORDERBYo.create_timeDESC,o.idDESC\G度量口径专用连接执行FLUSH STATUS的作用域与副作用按本机版本与手册核对FLUSHSTATUS;-- 跑上面任一条 SQL随后SHOWSESSIONSTATUSWHEREVariable_nameIN(Handler_read_key,Handler_read_next,Handler_read_rnd_next);预期输出两条 SQL 都返回 20 行EXPLAIN ANALYZE中内层对orders的实际扫描行数应为 800020改写后外层 join 只取 20 行Handler_read_next两者同在 80 万量级索引条目遍历量不变Handler_read_key预期由 80 万量级降到 20 量级。以上均为语义推算、未实测不同版本的 handler 计数口径可能有差异。实际输出在测试机执行后把\G输出原样贴回逐项填入第五节表格。若出现Using filesort先确认排序列是否被范围条件截断、索引是否覆盖取列修完再测。常见失败与修复只写ORDER BY create_time数据里时间戳大量重复 → 相邻两页出现相同行或漏行。原因是排序键不唯一与性能无关修复方式是内外层都写ORDER BY create_time DESC, id DESC。改写后比原写法更慢。原因是排序键本身是id原写法在聚簇索引上一次扫描直接拿行改写多了派生表与 join修复方式是该场景保持原写法不要套延迟关联。五、验证结果与边界指标改写前改写后口径说明返回行数待实测应为 20待实测应为 20以结果集行数为准Handler_read_next待实测待实测二级索引条目遍历量Handler_read_key待实测待实测回表次数本次改写的主要收益来源wall time 中位数≥5 次待实测待实测冷缓存与热缓存分开测慢日志rows_examined待实测待实测需开扩展字段参数名按本机SHOW VARIABLES核对边界与代价线上新增(create_time, id)索引要评估在线 DDL 时长、磁盘占用、从库延迟与回滚预案改写不解决COUNT(*)总数可改为缓存、按需加载或用LIMIT N1只判断“是否有下一页”EXPLAIN计划会随统计信息和版本升级变化需把rows_examined与 P99 纳入监控才能证明改写长期有效。参考资料MySQL 8.0 Reference ManualLIMIT Query OptimizationMySQL 8.0 Reference ManualEXPLAIN Statement含 EXPLAIN ANALYZEMySQL 8.0 Reference ManualStatus VariablesHandler_read_*MySQL 8.0 Reference ManualSwitchable Optimizationsoptimizer_switch
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Java后端WebUploader分块上传完整解析:接收合并与断点续传 2026/9/30 7:34:02

Java后端WebUploader分块上传完整解析:接收合并与断点续传

大文件上传这件事,很多Java后端开发第一次遇到"分块"这个概念,多半是在某个文件上传接口被运维找上门的时候——要么是用户传了个几百MB的视频,中间断一次网就全部重来;要么是Nginx直接返回413,前端疯狂报错…

阅读更多 →
Python+TensorFlow卷积神经网络实战:猫狗识别从70%到97%准确率 2026/9/30 7:34:02

Python+TensorFlow卷积神经网络实战:猫狗识别从70%到97%准确率

简介:这份资源面向深度学习入门者与计算机视觉初学者,围绕TensorFlow卷积神经网络实现猫狗图像二分类这一经典任务展开,帮助读者打通从数据读取到模型测试的完整流程。压缩包内共1个PDF文件,约78KB,以图文讲解形式呈现…

阅读更多 →
用PowerShell打造UniApp H5自动化打包部署脚本 2026/9/30 7:34:01

用PowerShell打造UniApp H5自动化打包部署脚本

前阵子给一个 UniApp 做的 H5 项目做发版,连续几周被同一件事折腾:本地打开 HBuilderX 手动点发行,等编译跑完再手动压缩,最后还得开 FTP 工具传服务器。这套流程看着不复杂,但每次少说也要十分钟,遇到线上…

阅读更多 →
Word粘贴内容如何过滤?富文本编辑器自定义规则实战 2026/9/30 7:34:01

Word粘贴内容如何过滤?富文本编辑器自定义规则实战

做网页富文本编辑器的人都清楚,用户从 Word 里复制一段内容再粘到网页里,是整个编辑器生命周期里最容易翻车的入口。我之前维护内部的在线文档系统时,收到最多的工单就是"从 Word 复制的表格又爆宽了""标题前面的编号全丢了&q…

阅读更多 →
Python实现AI人机对话:从环境配置到多轮对话与避坑指南 2026/9/30 7:34:01

Python实现AI人机对话:从环境配置到多轮对话与避坑指南

简介:这份PDF资源面向希望入门人工智能与自然语言处理的Python开发者,聚焦如何用Python搭建一套可运行的人机对话系统,解决从零实现类似“小娜”“Siri”交互效果的学习需求。资源包内仅含1个PDF文件,压缩包约145KB,以…

阅读更多 →
CNN人脸识别实战:从示例代码到门禁级部署的避坑指南 2026/9/30 7:33:54

CNN人脸识别实战:从示例代码到门禁级部署的避坑指南

简介:这份资源是一份面向深度学习初学者与计算机视觉入门者的卷积神经网络人脸识别示例代码文档,以PDF形式呈现,帮助读者理解如何从传统特征脸法过渡到CNN方案,并动手搭建可识别特定人脸的分类系统。压缩包内共1个PDF文件&#xf…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉