大数据量下查询显示优化方案小结
大数据量下查询显示优化方案小结
最近工作中,遇到了优化大批量数据查询和显示的问题,数据量在10W级别。经过反复设计和讨论,最终得到优化到了较为满意的效果,在此记录小结下,在解决此类问题中的思考。
问题背景说明
通常情况下,用户查询数据量不超过1千条,但有几个大户,通过某种方式,生成了上万级别的数据,前端未针对大数据量的查询和显示进行优化,导致该界面显示卡顿、白屏、点击无响应、显示总量和实际总量不一致等等问题。
总体思路
大数据量查询和显示,可从以下三个方面来优化:
- 定时分批查询
- 缓存中心
- UI优化
以上三层分别对应数据层、中间层和UI层,每层优化方案可单独使用,也可以结合使用。建议结合使用。
定时分批查询
数据后台提供的查询指令是支持分页查询的,与其相关的字段有:
- 查询方向 qryDir, 定位查询方向,1为往下查,0为往上查
- 请求行数 qryCount,说明期望查询的个数
- 定位串 qryPositionStr, 查询定位串,用于定位查询起点
基于上述三个字段,系统可以实现分页查询功能,但具体到UI层,因为无法获知该用户数据总量,也就无法推导总页数信息,UI层无法以分页形式展示。如果后台提供数据总量字段,那就可方便实现上一页、下一页、最后一页等功能。现有后台没提供总量信息,无法在UI层做分页查询,只在数据层做分页查询。
在数据层做分页查询时,对UI层是透明的。也就是说,当数据层尚未查询完毕时,UI层是不会收到响应,还可以继续操作界面。这里可以优化,同产品讨论以下两种处理方式:
- 方案一:本次查询尚未返回,禁止UI层重复查询
- 方案二:本次查询尚未返回,支持UI层重复查询
目前的设计采用方案二,因此需要数据层考虑重复请求的场景。除了重复请求之外,数据层还需要考虑后台限制和缓存管理,小结起来有以下三点:
- 后台限制
- 缓存管理
- 重复查询
下面分别阐述数据层针对上述问题的设计方案。
- 后台限制
任何后台服务能够提供的功能都是有限制要求的。数据后台对查询指令有单次最大条数和查询频率的限制,具体限制如下:
- 单次查询最大请求行数建议1000条
- 查询频率限制在5秒15笔查询,超频查询会报错
为了在不影响后台稳定性的情况下,尽可能快地查询回所有信息。在查询过程中,除了常规的分页查询之外,还额外增加定时查询机制,具体设计为,每连续发起3笔分页查询,等待半秒后,继续查询,循环往复,直到所有查询数据都返回,每次分页查询请求条数设置后台推荐的最大值。上述每3笔休息半秒需要根据后台具体限制来设置,考虑到系统还有其他查询指令在同时进行,设置宽松些比较合适。要明确这一点,在分页查询过程中触发流量超限错误,那么之前已查询的数据都会无效,要慎重设置定时间隔,宁可慢一点,不能出错。
- 缓存管理
此处的缓存指的是数据层的缓存,而不是缓存中心的缓存。此处的缓存用于保存分页查询返回的数据,等到所有查询完毕,再汇总往上传递。这里要注意的点,是要区分以下两种情况:
- 由缓存中心主动发起的分页查询
- 由数据层发起的分页查询
上述两种情况,单靠分页查询标志无法区分,需要增加额外的标识信息来区分,在缓存中心需要根据此标志进行查询数据的合并和过滤。
- 重复查询
当数据层查询尚未完成,又有重复查询的情况,按照场景可分为
- UI层发起新的全量查询
- 缓存中心定时发起增量查询
针对UI层发起新的全量查询,作为数据层有两种应对策略:
- 方案一:本次查询尚未完成,拒绝新的查询
- 方案二:本次查询尚未完成,允许新的查询,同时清空上次已查询得到的信息
由于数据层是服务提供方,提供给UI层和缓存中心层使用,由于UI层支持重复查询,缓存中心层支持定时查询,所以数据层采用第二种方案。
针对缓存中心定时发起增量查询,当上一次全量或者增量查询尚未返回时,缓存中心要禁止新的增量查询请求。
缓存中心
为了管理缓存以及提供缓存数据接口,设计缓存中心,它介于UI层和数据层之间,UI层通过订阅数据消息,来获得响应数据。查询中心在查询到数据后,通过发布通知的方式,更新界面数据。
在缓存中心中,对外提供两个公共接口:
- ForceRefreshData: 主动重新获取全量数据
- GetDataByAccount:按照账号获取全量数据
在缓存中心内部,通过定时器来触发分页查询。当查询完毕时,通过订阅发布机制向有需要的界面推送数据,推送的数据既包括全量数据,也包括增量数据,便于UI层使用。
在缓存中心处理过程中,增量数据的来源有两处:
- 定时增量请求
- 两次全量请求
无论是哪种来源,都需要和已有全量查询结果进行对比,得出增量数据。当数据量很大时,从新的全量查询和已有全量查询中对比获得增量数据,时间复杂度很高,比如全量有5W数据,新的全量有6W数据,那么从6W数据中去掉已有的5W数据,得到增量1W数据,双重循环的话,会操作6W*5W次,可通过stl::set
结构来加速过滤筛选,其中的key
设置为可唯一标识数据的属性组合即可。
缓存中心要考虑增量查询和全量查询冲突的问题,也就是说,要考虑以下表格中的四种场景:
当前的查询场景 | 增量查询 | 全量查询 |
---|---|---|
增量查询 | 增量-增量 | 增量-全量 |
全量查询 | 全量-增量 | 全量-增量 |
在具体实现时,建议缓存中心中的增量查询和全量查询复用同个请求,保存一份全量数据成员即可。
UI优化
UI优化可分为交互优化和刷新优化两个方面,以下分别来描述。
交互优化
为减少可能的重复全量查询,可在UI层提示用户,该界面会主动接受数据推送(实际实现,可能是定时查询、也可以是接收后台主推),无需频繁查询,但不禁止用户主动刷新。
另外的话,在用户主动刷新后,界面增加查询定时器,告知用户已查询耗时时间,已查询到的数据量,该数据量可按照每次查询1000条,根据底层逻辑来大概预估已查询到的数据条数,等到数据层全部查询完毕后,显示最终准确数值。
刷新优化
本次优化是在Windows
系统上,使用CListCtrl
控件的Report
模式来显示数据。刷新优化可以从以下三点出发:
- 虚表模式赋值
- 界面数据增量刷新
- 界面显示局部刷新
虚表模式赋值,是MFC中提供的加快列表控件显示速度的方案,具体参见 CListCtrl虚拟列表技术,此处不再叙述。
界面增量刷新,是指在UI层维护当前显示内容缓存,当有数据更新时,通过比较更新数据和当前缓存,决定是否更新列表。基于上述缓存中心,可通过增量更新来优化判断流程。
界面局部刷新,是指只针对当前界面显示部分进行刷新,未显示部分不进行刷新。界面局部刷新涉及到的函数有以下三个:
- int GetTopIndex() 获得当前显示区域最顶部Item的位置索引
- int GetCountPerPage() 获得当前控件显示区域最多能够显示Item的个数
- BOOL RedrawItems(int nFirst, int nLast) 重绘位置索引在nFirst和nLast区间中的Item
具体使用很简单,在重绘时,只需要以下两句话搞定:
int nFirst = GetTopIndex();
RedrawItems(nFirst, nFirst + GetCountPerPage())
以上方案实现了界面显示局部刷新,配合虚表模式赋值和界面数据增量刷新,可极大提高在大数据量下的CListCtrl
刷新显示效果,亲测有效,值得使用。
全文总结
本文总结大数据量下的查询和显示优化方案,主要从数据层、中间层和UI层进行优化,方案设计思路如下:
- 数据层的定时分批查询
- 根据后台限制优化定时查询间隔
- 缓存管理
- 考虑重复查询场景
- 中间层的缓存中心
- 支持定时增量查询
- 推送数据既包括全量也包括增量
- UI层的交互优化和刷新优化
- 交互优化
- 虚表模式赋值
- 界面数据增量刷新
- 界面显示局部刷新
以上是本人在大数据量下查询和显示优化方案的思考,文章可能会有纰漏和错误,欢迎大家在留言中指出来,大家一起讨论学习,共同进步!