Mongodb分页查询优化下
上一篇文章中分析分页TOP N如何进行创建索引以及不同索引对性能影响,随着数据量N级增长,不修改SQL业务逻辑,会存在不同集合或索引热点问题,经过修改业务逻辑,不管数据量如何增长,TOP N查询性能基本上保持在几十毫秒水平.使得在高并发下满足业务SLA要求.本次文章接着讲翻页性能优化.skip针对大结果下,通过改写可以获取相对稳定执行时间与效率,否则使用skip性能随着翻页越大,呈现性能瓶颈.
【分页翻页案例以及执行效率】
1、分页翻页,尤其是结果集特别多越往后翻页越慢,常规写法db.collection.find({query}).sort({name:1}).skip(N).limit(50)
2、ESR索引下sql分页以及执行效率【ESR方式】
db.test.find({org:"10000", signT:{
【ESR索引翻页执行效率】
翻第二页(每页50条)
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 29,
"totalKeysExamined" : 876,
"totalDocsExamined" : 100,
翻第10页(每页50条)
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 1001,
"totalKeysExamined" : 10809,
"totalDocsExamined" : 500,
翻第100页(每页50条)
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 12830,
"totalKeysExamined" : 108725,
"totalDocsExamined" : 5000
索引:org:1,no:1,signT:1,翻页从第一页到100页,执行时间从29ms到12830ms.其实100页数据才5000条,但是totalKeysExamined检查是108725,此时返回5000条,相当于indexkey:doc=20:1,显然是低效索引的。正常是indexkey:returndoc=1:1是完美的索引.索引不能说ESR索引效率高,最好是ES效率(需要修改业务逻辑).
3、ER索引后sql执行效率【ER】
【ER索引翻页执行效率】
翻第二页:
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 1037,
"totalKeysExamined" : 26973,
"totalDocsExamined" : 26973,
翻第10页:
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 85,
"totalKeysExamined" : 26973,
"totalDocsExamined" : 26973,
翻第100页:
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 106,
"totalKeysExamined" : 26973,
"totalDocsExamined" : 26973,
翻第500页:
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 157,
"totalKeysExamined" : 26973,
"totalDocsExamined" : 26973,
}).sort({no:1}).skip(50).limit(50).explain("executionStats")
索引:org:1,signT,翻页从第一页到100页,执行时间从1037ms到157ms.第一次慢的主要返回索引记录,因为索引顺序与排序顺序不一致,导致不管翻多少页,每次检查记录数与索引数是一致,但是返回只有50条,此时返回记录始终都是26973,排序后返回50条,,显然是索引效率与回表返回记录来说,效率也不高的。正常是indexkey:returndoc=1:1是完美的索引.如果用户想要快速获取第一页的记录或者前面几页,ESR效率要高于ER,最好是ES效率(需要修改业务逻辑).
4、修改业务代码后高效索引下sql分页以及执行效率【ES方式】
db.test.find({org:"10000", staDate: ISODate("2020-07-17T00:00:00.000+08:00"), signStatus:{$in:[ 0, 1 ] } }).sort({no:1}).skip(50).limit(50).explain("executionStats")
【ES索引翻页效率最高】
翻第二页(每页50条)
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 20,
"totalKeysExamined" : 100,
"totalDocsExamined" : 100
翻第10页(每页50条)
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 82,
"totalKeysExamined" : 500,
"totalDocsExamined" : 500,
翻第100页(每页50条)
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 716,
"totalKeysExamined" : 5000,
"totalDocsExamined" : 5000
翻500页(每页50条)
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 1104,
"totalKeysExamined" : 25000,
"totalDocsExamined" : 25000
高效索引:org:1,no:1,staDate:1,翻页从第一页到500页,执行时间从20ms到1104ms.其实500页数据才25000条,如果单页显示100条,翻500页,执行时间更长,此时还是在高效索引以及代码修改后效果.此时ES索引.
4、以上除了ES索引下从第一页到500页,ESR执行时间最大是1s,ESR翻100页,执行时间已经接近13s,ER或索引,显然翻页N越大,呈现性能越差.小翻页下性能尚且能接受,大翻页下性能肯定是需要优化,如果代码不能ES索引,那么ESR或ER效率都不高的情况,需要分析结果集大小,如果结果集小,ESR效率相对好些,如果结果集非常大,ESR或者ER索引都存在瓶颈.
ESR:瓶颈索引是热点,索引需要过滤R条件部分,大部分记录不满足条件,性能在索引过滤上。
ER:瓶颈返回索引记录与回表记录是1:1,但需要结果集大部分丢弃.最终索引与集合都在瓶颈 ,尤其索引与集合太大,需要从磁盘加载 。
有没有什么写法能够实现翻页呈现稳定性能,有的,但是有一定条件限制.
【分页翻页案例改写以及性能】
1、分页翻页,尤其是结果集特别多越往后翻页越慢,常规写法db.collection.find({query}).sort({id:1}).skip(N).limit(50),如果id是唯一或者想办法使用唯一列来排序,此时可以将翻页语句修改如下:
db.test.find({org:"10000",staDate:ISODate("2020-07-17T00:00:00.000+08:00"), signStatus:{
这里N是上页最后一个值,取下一页之前需要获取上一页最大值且无需skip关键字.
2、代码逻辑(shell演示)
var firstpage=db.test.find({org:"10000", staDate: ISODate("2020-07-17T00:00:00.000+08:00"), signStatus:{$in:[ 0, 1 ] } }).sort({no:1}).limit(50)
var latest=null;
//获取第一页最后一条记录
while (firstpage.hasNext()){
latest= firstpage.next();
print(tojson(latest))};
第50条记录: no:XX2653808
//获取下一页
db.test.find({org:"10000", staDate: ISODate("2020-07-17T00:00:00.000+08:00"), signStatus:{
3、验证执行效率
翻第二页:
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 13,
"totalKeysExamined" : 50,
"totalDocsExamined" : 50
翻第10页:
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 14,
"totalKeysExamined" : 50,
"totalDocsExamined" : 50,
翻第100页:
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 16,
"totalKeysExamined" : 50,
"totalDocsExamined" : 50,
对应执行计划:
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 50,
"executionTimeMillis" : 16,
"totalKeysExamined" : 50,
"totalDocsExamined" : 50,
"executionStages" : {
"stage" : "LIMIT",
"nReturned" : 50,
"executionTimeMillisEstimate" : 0,
"works" : 51,
"advanced" : 50,
"needTime" : 0,
"needYield" : 0,
"saveState" : 2,
"restoreState" : 2,
"isEOF" : 1,
"limitAmount" : 50,
"inputStage" : {
"stage" : "FETCH",
"filter" : {
"signStatus" : {
"$in" : [
0,
1
]
}
},
"nReturned" : 50,
"executionTimeMillisEstimate" : 0,
"works" : 50,
"advanced" : 50,
"needTime" : 0,
"needYield" : 0,
"saveState" : 2,
"restoreState" : 2,
"isEOF" : 0,
"docsExamined" : 50,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 50,
"executionTimeMillisEstimate" : 0,
"works" : 50,
"advanced" : 50,
"needTime" : 0,
"needYield" : 0,
"saveState" : 2,
"restoreState" : 2,
"isEOF" : 0,
"keyPattern" : {
"org" : 1,
"staDate" : 1,
"no" : 1
},
"indexName" : "org_1_staDate_1_no_1"
4、取消skip方式,对排序列增加一个大于上一页最大值来快速获取分页,性能基本上在10-20ms之间.
【分页与翻页总结】
1、分页以及翻页需要配合最佳索引才能获取 最佳SLA性能,否则分页与翻页随着结果集增长 ,性能会呈现瓶颈。
2、可以对skip进行改写来获取稳定SLA性能,针对排序列使用大于上一页的最大值来实现,此时排序列需要唯一特性才可以,否则会出现重复数据情况
3、不管ESR、ER还是ES,都需要具体问题具体分析,例如索引key扫描、回表扫描记录数、返回记录数都分析他们之间比例,1:1:1是最好性能,随着数据增长,瓶颈在索引、还是在集合中.
4、并不是所有分页、翻页SQL都可以优化到最佳性能,主要取决于SQL写法以及对索引原理了解(能否创建出高效索引),最大问题在于SQL写法.
- memcache安装方法
- 设计模式专题(五)——工厂方法模式
- ASP.NET AJAX(11)__ScriptManagerUpdatePanel的支持成员功能控制成员脚本控件支持成员ScriptMode和ScriptPathLoadScriptsBeforeU
- SQL Server 2016新特性:动态数据屏蔽(DDM)
- ASP.NET AJAX(12)__浏览器兼容功能判断浏览器的类型和版本Sys.Browser针对DOM元素的兼容操作针对DOM事件的兼容操作
- 设计模式专题(六)——原型模式
- ASP.NET AJAX(13)__利用Microsoft AJAX Library开发客户端组件Sys.Component成员Sys.IDisposable成员Sys.INotifyDisposin
- 设计模式专题(七)——建造者模式
- ASP.NET AJAX(14)__UpdatePanel与服务器端脚本控件脚本控件的作用脚本控件的指责Extender模型脚本控件和Extender模型在PostBack中保持状态在UpdatePa
- ASP.NET AJAX(15)__构建高性能ASP.NET AJAX应用UpdatePanel的性能问题使用UpdatePanel的注意事项脚本加载避免脚本阻塞页面显示AjaxControlTool
- LINQ to SQL(1):基础入门
- 设计模式专题(十)——观察者模式
- LINQ to SQL(2):生成对象模型
- 使用 Oracle 的 Security External Password Store 功能实现数据库加密登陆
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Weblogic12c T3 协议安全漫谈
- 联盟链智能合约安全浅析
- MySQL复杂where条件分析
- 超链接标签
- 【剑指Offer】二叉树的镜像
- 【redis6.0.6】redis源码慢慢学,慢慢看 -- 第四天:提纲掣领main函数(server)
- redis学习(六)
- 原生JS实现一个Ajax跨域请求
- 深入理解类加载机制:拨开迷雾见真章
- 修复postgres安装错误 Problem running post-install step. Installation may not complete correctly The datab
- automagica 调用windows画图以及登录qq
- 数据库PostrageSQL-高级特性
- 数据库PostgreSQL-安装
- CentOS 7 安装 PHP 7.4.0 正式版
- ABAP实现设计模式里的观察者-发布者模式