窗口函数常用用于分组排序运算中,方便用户实现各种分组需求。由于窗口函数需要通常需要全表扫描数据,同时还需排序聚集,消耗大量的CPU资源,执行效率较低。以下介绍一例窗口函数的优化案例。

准备例子

有这样一个功能需求。系统中存在资讯信息这样一个模块,用于发布一些和业务相关的活动动态,其中每条资讯信息都有一个所属类型(如科技类的资讯、娱乐类、军事类···)和浏览量字段。官网上需要滚动展示一些热门资讯信息列表(浏览量越大代表越热门),而且每个类别的相关资讯记录至多显示3条,换句话:“按照资讯分类分组,取每组的前3条资讯信息列表”。 表结构及初始数据如下:

Create table info(id numeric not null primary key ,title varchar(100) ,Viewnum numeric ,info_type_id numeric ,Code text
);create index info_infotypeid on info (info_type_id);Create table info_type(Id numeric  not null primary key,Name varchar(100)
);--插入100个新闻分类
Insert into info_type select id, 'TYPE' || lpad(id::text, 5, '0' ) from generate_series(1, 100) id;--插入1000000个新闻
Insert into info select id, 'TTL' || lpad(id::text, 20, '0' ) title, ceil(random()*1000000) Viewnum, ceil(random()*100) info_type_id , md5(id) code from generate_series(1, 1000000) id;vacuum analyse info_type,info;

方法一:使用窗口函数

explain (analyse ,buffers )
with i as ( select i.*, row_number() over (partition by i.info_type_id order by i.viewnum desc) sn from info i)
select * from info_type t left join i on i.sn <= 3 and i.info_type_id = t.id;QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------Hash Right Join  (cost=211867.09..245279.17 rows=333333 width=97) (actual time=4223.126..6169.377 rows=300 loops=1)Hash Cond: (i.info_type_id = t.id)Buffers: shared hit=11582 read=1753, temp read=17860 written=17901->  Subquery Scan on i  (cost=211863.84..244363.84 rows=333333 width=82) (actual time=4223.080..6168.742 rows=300 loops=1)Filter: (i.sn <= 3)Rows Removed by Filter: 999700Buffers: shared hit=11582 read=1752, temp read=17860 written=17901->  WindowAgg  (cost=211863.84..231863.84 rows=1000000 width=82) (actual time=4223.079..6080.518 rows=1000000 loops=1)Buffers: shared hit=11582 read=1752, temp read=17860 written=17901->  Sort  (cost=211863.84..214363.84 rows=1000000 width=74) (actual time=4223.065..5224.438 rows=1000000 loops=1)Sort Key: i_1.info_type_id, i_1.viewnum DESCSort Method: external merge  Disk: 84128kBBuffers: shared hit=11582 read=1752, temp read=17860 written=17901->  Seq Scan on info i_1  (cost=0.00..23334.00 rows=1000000 width=74) (actual time=0.006..249.981 rows=1000000 loops=1)Buffers: shared hit=11582 read=1752->  Hash  (cost=2.00..2.00 rows=100 width=15) (actual time=0.037..0.037 rows=100 loops=1)Buckets: 1024  Batches: 1  Memory Usage: 13kBBuffers: shared read=1->  Seq Scan on info_type t  (cost=0.00..2.00 rows=100 width=15) (actual time=0.015..0.021 rows=100 loops=1)Buffers: shared read=1Planning Time: 0.328 msExecution Time: 6182.496 ms
(22 rows)

可以看到,这里消耗资源最大的是在 sort 操作上。那么,我们能否避免sort 操作了? 使用索引可以避免sort 操作。

方法二:只取第3名的记录

方法一,由于读取了大量数据块,耗时过多。本方法暂时先简化例子,功能要求只需返回每组1条记录。新的SQL特点,每个类型使用子查询通过info表的info_type_id列的索引,可以避免读取多余的数据。select list的子查询作为计算列,只能返回一个值,所以使用row (i.*)::info 先整合,然后使用 (inf).* 再分解,同时使用 offset2 limit 1获取第三名的一行记录。

explain (analyse ,buffers )
select id, name, (inf).*
from (select t.*,(select row (i.*)::infofrom info iwhere i.info_type_id = t.idorder by i.viewnum descoffset 2limit 1) inffrom info_type t) t;QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------Seq Scan on info_type t  (cost=0.00..6708942.94 rows=100 width=361) (actual time=127.552..10513.868 rows=100 loops=1)Buffers: shared hit=3544406 read=3255SubPlan 1->  Limit  (cost=13417.88..13417.88 rows=1 width=38) (actual time=21.744..21.745 rows=1 loops=100)Buffers: shared hit=706280 read=3252->  Sort  (cost=13417.87..13442.87 rows=10000 width=38) (actual time=21.740..21.740 rows=3 loops=100)Sort Key: i.viewnum DESCSort Method: top-N heapsort  Memory: 25kBBuffers: shared hit=706280 read=3252->  Bitmap Heap Scan on info i  (cost=185.93..13288.63 rows=10000 width=38) (actual time=3.985..18.371 rows=10000 loops=100)Recheck Cond: (info_type_id = t.id)Heap Blocks: exact=706728Buffers: shared hit=706280 read=3252->  Bitmap Index Scan on info_infotypeid  (cost=0.00..183.43 rows=10000 width=0) (actual time=2.615..2.615 rows=10000 loops=100)Index Cond: (info_type_id = t.id)Buffers: shared hit=1272 read=1532SubPlan 2->  Limit  (cost=13417.88..13417.88 rows=1 width=38) (actual time=20.599..20.600 rows=1 loops=100)Buffers: shared hit=709529 read=3->  Sort  (cost=13417.87..13442.87 rows=10000 width=38) (actual time=20.595..20.595 rows=3 loops=100)Sort Key: i_1.viewnum DESCSort Method: top-N heapsort  Memory: 25kBBuffers: shared hit=709529 read=3->  Bitmap Heap Scan on info i_1  (cost=185.93..13288.63 rows=10000 width=38) (actual time=3.640..17.373 rows=10000 loops=100)Recheck Cond: (info_type_id = t.id)Heap Blocks: exact=706728Buffers: shared hit=709529 read=3->  Bitmap Index Scan on info_infotypeid  (cost=0.00..183.43 rows=10000 width=0) (actual time=2.291..2.291 rows=10000 loops=100)Index Cond: (info_type_id = t.id)Buffers: shared hit=2801 read=3SubPlan 3->  Limit  (cost=13417.88..13417.88 rows=1 width=38) (actual time=21.284..21.285 rows=1 loops=100)Buffers: shared hit=709532->  Sort  (cost=13417.87..13442.87 rows=10000 width=38) (actual time=21.279..21.279 rows=3 loops=100)Sort Key: i_2.viewnum DESCSort Method: top-N heapsort  Memory: 25kBBuffers: shared hit=709532->  Bitmap Heap Scan on info i_2  (cost=185.93..13288.63 rows=10000 width=38) (actual time=3.609..17.868 rows=10000 loops=100)Recheck Cond: (info_type_id = t.id)Heap Blocks: exact=706728Buffers: shared hit=709532->  Bitmap Index Scan on info_infotypeid  (cost=0.00..183.43 rows=10000 width=0) (actual time=2.267..2.267 rows=10000 loops=100)Index Cond: (info_type_id = t.id)Buffers: shared hit=2804SubPlan 4->  Limit  (cost=13417.88..13417.88 rows=1 width=38) (actual time=20.763..20.763 rows=1 loops=100)Buffers: shared hit=709532->  Sort  (cost=13417.87..13442.87 rows=10000 width=38) (actual time=20.759..20.759 rows=3 loops=100)Sort Key: i_3.viewnum DESCSort Method: top-N heapsort  Memory: 25kBBuffers: shared hit=709532->  Bitmap Heap Scan on info i_3  (cost=185.93..13288.63 rows=10000 width=38) (actual time=3.769..17.505 rows=10000 loops=100)Recheck Cond: (info_type_id = t.id)Heap Blocks: exact=706728Buffers: shared hit=709532->  Bitmap Index Scan on info_infotypeid  (cost=0.00..183.43 rows=10000 width=0) (actual time=2.390..2.390 rows=10000 loops=100)Index Cond: (info_type_id = t.id)Buffers: shared hit=2804SubPlan 5->  Limit  (cost=13417.88..13417.88 rows=1 width=38) (actual time=20.713..20.713 rows=1 loops=100)Buffers: shared hit=709532->  Sort  (cost=13417.87..13442.87 rows=10000 width=38) (actual time=20.709..20.709 rows=3 loops=100)Sort Key: i_4.viewnum DESCSort Method: top-N heapsort  Memory: 25kBBuffers: shared hit=709532->  Bitmap Heap Scan on info i_4  (cost=185.93..13288.63 rows=10000 width=38) (actual time=3.689..17.432 rows=10000 loops=100)Recheck Cond: (info_type_id = t.id)Heap Blocks: exact=706728Buffers: shared hit=709532->  Bitmap Index Scan on info_infotypeid  (cost=0.00..183.43 rows=10000 width=0) (actual time=2.288..2.288 rows=10000 loops=100)Index Cond: (info_type_id = t.id)Buffers: shared hit=2804Planning Time: 0.729 msExecution Time: 10514.326 ms
(74 rows)

方法二针对 info_type 的每一行,info 表都要根据 info_type_id 索引访问 info 表 5 次 (5个列)。 总时间消耗: 100 (行)*5(列)* 20 (每次大概20ms),大约 10000ms。

执行计划分析:根据 info_type_id 索引,需要访问的行数太多,而且还是需要排序。基于这些考虑,我们可以创建个 info_type_id + viewnum 复合索引,减少每访问的时间消耗,避免排序。

方法三:优化索引

create index info_typeview on info(info_type_id,viewnum);QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------Seq Scan on info_type t  (cost=0.00..4627.72 rows=100 width=361) (actual time=0.255..13.391 rows=100 loops=1)Buffers: shared hit=2881 read=120SubPlan 1->  Limit  (cost=6.31..9.25 rows=1 width=38) (actual time=0.041..0.041 rows=1 loops=100)Buffers: shared hit=480 read=120->  Index Scan Backward using info_typeview on info i  (cost=0.42..29421.91 rows=10000 width=38) (actual time=0.034..0.040 rows=3 loops=100)Index Cond: (info_type_id = t.id)Buffers: shared hit=480 read=120SubPlan 2->  Limit  (cost=6.31..9.25 rows=1 width=38) (actual time=0.022..0.022 rows=1 loops=100)Buffers: shared hit=600->  Index Scan Backward using info_typeview on info i_1  (cost=0.42..29421.91 rows=10000 width=38) (actual time=0.018..0.021 rows=3 loops=100)Index Cond: (info_type_id = t.id)Buffers: shared hit=600SubPlan 3->  Limit  (cost=6.31..9.25 rows=1 width=38) (actual time=0.021..0.021 rows=1 loops=100)Buffers: shared hit=600->  Index Scan Backward using info_typeview on info i_2  (cost=0.42..29421.91 rows=10000 width=38) (actual time=0.018..0.020 rows=3 loops=100)Index Cond: (info_type_id = t.id)Buffers: shared hit=600SubPlan 4->  Limit  (cost=6.31..9.25 rows=1 width=38) (actual time=0.021..0.021 rows=1 loops=100)Buffers: shared hit=600->  Index Scan Backward using info_typeview on info i_3  (cost=0.42..29421.91 rows=10000 width=38) (actual time=0.018..0.020 rows=3 loops=100)Index Cond: (info_type_id = t.id)Buffers: shared hit=600SubPlan 5->  Limit  (cost=6.31..9.25 rows=1 width=38) (actual time=0.023..0.023 rows=1 loops=100)Buffers: shared hit=600->  Index Scan Backward using info_typeview on info i_4  (cost=0.42..29421.91 rows=10000 width=38) (actual time=0.020..0.022 rows=3 loops=100)Index Cond: (info_type_id = t.id)Buffers: shared hit=600Planning Time: 0.730 msExecution Time: 13.552 ms
(34 rows)

可以看到,创建新索引后,单次的访问从 20ms 降低到 0.023ms ,将近降了 1000 倍。

存在问题:限制了返回行数,仅一行,同时info表有5个列,所以有5个subplan,其中4个是冗余的。

方法四:使用array,一次返回多行

以下再修改新的SQL,新的SQL特点,select list的子查询作为计算列,只能返回一行值,所以使用array() 先转换成数组类型,然后使用 unnest() 再分解成多行,同时使用  limit 3获取前三名的三行记录。

explain (analyse ,buffers )
select id, name, (inf).*
from (select t.id, t.name, unnest(inf) inffrom (select t.*,array(select row (i.*)::infofrom info iwhere i.info_type_id = t.idorder by i.viewnum desclimit 3) inffrom info_type t) t) t;QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------Subquery Scan on t  (cost=0.00..942.89 rows=1000 width=361) (actual time=0.092..2.526 rows=300 loops=1)Buffers: shared hit=601->  ProjectSet  (cost=0.00..932.89 rows=1000 width=47) (actual time=0.089..2.406 rows=300 loops=1)Buffers: shared hit=601->  Seq Scan on info_type t_1  (cost=0.00..2.00 rows=100 width=15) (actual time=0.008..0.020 rows=100 loops=1)Buffers: shared hit=1SubPlan 1->  Limit  (cost=0.42..9.25 rows=3 width=38) (actual time=0.018..0.021 rows=3 loops=100)Buffers: shared hit=600->  Index Scan Backward using info_typeview on info i  (cost=0.42..29421.91 rows=10000 width=38) (actual time=0.017..0.020 rows=3 loops=100)Index Cond: (info_type_id = t_1.id)Buffers: shared hit=600Planning Time: 0.295 msExecution Time: 2.639 ms
(14 rows)

方法五:使用lateral

新的SQL特点,简洁迅速,使用LATERAL子查询,允许它们引用前面的FROM项提供的列。

explain(analyze ,buffers )
select t.*, inf.*
from info_type tleft join LATERAL (select i.*from info iwhere i.info_type_id = t.idorder by i.viewnum desc offset 0limit 3) inf on true;QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------Nested Loop Left Join  (cost=0.42..1267.72 rows=300 width=88) (actual time=0.026..1.087 rows=264 loops=1)Buffers: shared hit=554->  Seq Scan on info_type t  (cost=0.00..2.00 rows=100 width=15) (actual time=0.005..0.011 rows=100 loops=1)Buffers: shared hit=1->  Limit  (cost=0.42..12.60 rows=3 width=73) (actual time=0.007..0.010 rows=2 loops=100)Buffers: shared hit=553->  Index Scan Backward using info_typeview on info i  (cost=0.42..406.17 rows=100 width=73) (actual time=0.007..0.010 rows=2 loops=100)Index Cond: (info_type_id = t.id)Buffers: shared hit=553Planning Time: 0.114 msExecution Time: 1.114 ms
(11 行记录)

结论

  1. 整个优化关键点是创建了 info_type_id + viewnum 复合索引,也就是窗口查询 partition by 和 order by 两部分列的复合索引。
  2. array 的应用也是关键的地方,解决了需要返回多行的问题。
  3. 多个subplan在嵌套了 array 之后,变成 1 个。

窗口函数查询优化案例相关推荐

  1. mysql——查询优化案例计算

    文章目录 mysql--查询优化案例计算 前言 准备 成本分析 成本计算 全表扫描的成本 走shop_id索引的成本 mysql--查询优化案例计算 前言 上一篇博客说到查询优化器,但是并没有说查询成 ...

  2. MySQL六种窗口函数用法案例

    Java和大数据系列 注:大家觉得博客好的话,别忘了点赞收藏呀,本人每周都会更新关于人工智能和大数据相关的内容,内容多为原创,Python Java Scala SQL 代码,CV NLP 推荐系统等 ...

  3. mysql 慢查询sql实例_MySQL慢查询优化案例一

    这是学习笔记的第1946篇文章 最近整理了一个慢日志排行榜,可以从一个整体的角度来看到整个数据库方向的慢日志情况,我的初步目标是坚持一段时间,每天争取优化一个慢查询语句.通过这个过程来梳理一些SQL性 ...

  4. mysql相邻行数据计算的自定义变量@和Lead窗口函数的具体案例适应版本mysq5.7 mysql8.0

    Mysql相邻数据(行)计算的自定义变量与Lead Lag窗口函数的案例 1 相邻行 我们在处理数据时有时需要对业务上定义的相邻行进行统计计算. 比如我们想统计公司里所有部门最近2年或相邻年份)的成本 ...

  5. hive窗口函数入门

    窗口函数over简介 先来看一下这个需求:求每个部门的员工信息以及部门的平均工资.在mysql中如何实现呢 SELECT emp.*, avg_sal FROM empJOIN (SELECT dep ...

  6. mysql8 rank_MySQL8.0窗口函数之排名函数(rank、dense_rank)的使用

    窗口函数简介MySQL从8.0开始支持开窗函数,这个功能在大多商业数据库中早已支持,也叫分析函数. 开窗函数与分组聚合比较像,分组聚合是通过制定字段将数据分成多份,每一份执行聚合函数,每份数据返回一条 ...

  7. SQL窗口函数-MySQL-leetcode刷数据库题目必备知识

    目录 背景: 窗口函数的介绍: 窗口函数的应用场景: 支持窗口函数的查询元素: 窗口函数的案例博客链接: 背景: 在leetcode刷数据库题目时,凡是中等难度及困难难度的题目必涉及到SQL窗口函数的 ...

  8. 全方位揭秘!大数据从0到1的完美落地之Hive窗口函数

    窗口函数 窗口函数over简介 先来看一下这个需求:求每个部门的员工信息以及部门的平均工资.在mysql中如何实现呢 求部门平均工资 select deptno ,avg(sal) from emp ...

  9. SQL 审核:基于PG数据库插件hook的SQL规范审核工具

    关注"数据和云",精彩不容错过 内容来源:2017 年 10 月 21 日,平安科技数据库架构师陈刚在"PostgreSQL 2017中国技术大会"进行< ...

最新文章

  1. 洛谷P2763 试题库问题
  2. jquery 使用小技巧
  3. 把远程仓库的项目,clone到eclipse里面
  4. 同时寻找最大数和最小数的最优算法 第二大数
  5. Maven系列(一):maven基础入门
  6. 牛客小白月赛9 A签到(乘法逆元)
  7. 很感谢你能来,不遗憾你离开(好文章)
  8. 火狐浏览器将网页保存为pdf
  9. dimens文件生成器使用方法
  10. docker笔记(转自:陈沙克日志)
  11. 如何让Python画笔画一个圆
  12. 阿里 前端 规范_阿里前端开发规范
  13. 什么事数据对象以及属性分为什么类型?
  14. mysql里hdr是什么的缩写_4kHDR是什么意思(4K HDR到底是什么鬼?)
  15. javaee.jar与servlet-api.jar
  16. 【数据结构】链表的原理及java实现
  17. 家用打印机助力返校季 惠普发布《亚洲儿童学习白皮书》
  18. [SolidWorks二次开发]特征造型——拉伸(2)
  19. 弘辽科技:优化直通车关键词,质量分降低点击单价。
  20. IOS 适配的几种模式

热门文章

  1. 忠实通物流信息管理系统
  2. 关于深入理解Java线程
  3. Excel笔记(2)常用函数1-10
  4. PPT模板下载100套欧美风云盘下载
  5. FOne easyModelVerifier™ 模型/代码Back-to-Back自动化验证工具
  6. win10自带虚拟机的窗口太小的调整办法,亲测有效
  7. 854计算机专业基础,东华大学2019年考研854计算机及软件工程专业基础综合考试大纲...
  8. 10讲学会C语言之第一讲:编程前的准备
  9. 电脑上的软件卸载不了怎么办
  10. 微信公众号主体注销了,如何办理账号迁移?