PgSQL · 实战经验 · 分组TOP性能提升44倍

业务背景

按分组取出TOP值,是非常常见的业务需求。

比如提取每位歌手的下载量TOP 10的曲目、提取每个城市纳税前10的人或企业。

传统方法

传统的方法是使用窗口查询,PostgreSQL是支持窗口查询的。

例子

测试表和测试数据,生成10000个分组,1000万条记录。

postgres=# create table tbl(c1 int, c2 int, c3 int);
CREATE TABLE
postgres=# create index idx1 on tbl(c1,c2);
CREATE INDEX
postgres=# insert into tbl select mod(trunc(random()*10000)::int, 10000), trunc(random()*10000000) from generate_series(1,10000000);
INSERT 0 10000000

使用窗口查询的执行计划

postgres=# explain select * from (select row_number() over(partition by c1 order by c2) as rn,* from tbl) t where t.rn<=10;
                                       QUERY PLAN
----------------------------------------------------------------------------------------
 Subquery Scan on t  (cost=0.43..770563.03 rows=3333326 width=20)
   Filter: (t.rn <= 10)
   ->  WindowAgg  (cost=0.43..645563.31 rows=9999977 width=12)
         ->  Index Scan using idx1 on tbl  (cost=0.43..470563.72 rows=9999977 width=12)
(4 rows)

使用窗口查询的结果举例

postgres=# select * from (select row_number() over(partition by c1 order by c2) as rn,* from tbl) t where t.rn<=10;
 rn |  c1  |   c2   | c3
----+------+--------+----
  1 |    0 |   1657 |
  2 |    0 |   3351 |
  3 |    0 |   6347 |
  4 |    0 |  12688 |
  5 |    0 |  16991 |
  6 |    0 |  19584 |
  7 |    0 |  24694 |
  8 |    0 |  36646 |
  9 |    0 |  40882 |
 10 |    0 |  41599 |
  1 |    1 |  14465 |
  2 |    1 |  29032 |
  3 |    1 |  39969 |
  4 |    1 |  41094 |
  5 |    1 |  69481 |
  6 |    1 |  70919 |
  7 |    1 |  75575 |
  8 |    1 |  81102 |
  9 |    1 |  87496 |
 10 |    1 |  90603 |
......

使用窗口查询的效率,20.1秒

postgres=# explain (analyze,verbose,costs,timing,buffers) select * from (select row_number() over(partition by c1 order by c2) as rn,* from tbl) t where t.rn<=10;
                                                                     QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
 Subquery Scan on t  (cost=0.43..770563.03 rows=3333326 width=20) (actual time=0.040..20813.469 rows=100000 loops=1)
   Output: t.rn, t.c1, t.c2, t.c3
   Filter: (t.rn <= 10)
   Rows Removed by Filter: 9900000
   Buffers: shared hit=10035535
   ->  WindowAgg  (cost=0.43..645563.31 rows=9999977 width=12) (actual time=0.035..18268.027 rows=10000000 loops=1)
         Output: row_number() OVER (?), tbl.c1, tbl.c2, tbl.c3
         Buffers: shared hit=10035535
         ->  Index Scan using idx1 on public.tbl  (cost=0.43..470563.72 rows=9999977 width=12) (actual time=0.026..11913.677 rows=10000000 loops=1)
               Output: tbl.c1, tbl.c2, tbl.c3
               Buffers: shared hit=10035535
 Planning time: 0.110 ms
 Execution time: 20833.747 ms
(13 rows)

雕虫小技

如何优化?

可以参考我之前写的,使用递归查询,优化count distinct的方法。

本文同样需要用到递归查询,获得分组ID

postgres=# with recursive t1 as (
postgres(#  (select min(c1) c1 from tbl )
postgres(#   union all
postgres(#  (select (select min(tbl.c1) c1 from tbl where tbl.c1>t.c1) c1 from t1 t where t.c1 is not null)
postgres(# )
postgres-# select * from t1;

写成SRF函数,如下

postgres=# create or replace function f() returns setof tbl as $$
postgres$# declare
postgres$#   v int;
postgres$# begin
postgres$#   for v in with recursive t1 as (
postgres$#    (select min(c1) c1 from tbl )
postgres$#     union all
postgres$#    (select (select min(tbl.c1) c1 from tbl where tbl.c1>t.c1) c1 from t1 t where t.c1 is not null)
postgres$#   )
postgres$#   select * from t1
postgres$#   LOOP
postgres$#     return query select * from tbl where c1=v order by c2 limit 10;
postgres$#   END LOOP;
postgres$# return;
postgres$#
postgres$# end;
postgres$# $$ language plpgsql strict;
CREATE FUNCTION

优化后的查询结果例子

postgres=# select * from f();
  c1  |   c2   | c3
------+--------+----
    0 |   1657 |
    0 |   3351 |
    0 |   6347 |
    0 |  12688 |
    0 |  16991 |
    0 |  19584 |
    0 |  24694 |
    0 |  36646 |
    0 |  40882 |
    0 |  41599 |
    1 |  14465 |
    1 |  29032 |
    1 |  39969 |
    1 |  41094 |
    1 |  69481 |
    1 |  70919 |
    1 |  75575 |
    1 |  81102 |
    1 |  87496 |
    1 |  90603 |
......

优化后,只需要464毫秒返回10000个分组的TOP 10。

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from f();
                                                     QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
 Function Scan on public.f  (cost=0.25..10.25 rows=1000 width=12) (actual time=419.218..444.810 rows=100000 loops=1)
   Output: c1, c2, c3
   Function Call: f()
   Buffers: shared hit=170407, temp read=221 written=220
 Planning time: 0.037 ms
 Execution time: 464.257 ms
(6 rows)

小结

  1. 传统的方法使用窗口查询,输出多个每个分组的TOP 10,需要扫描所有的记录。效率较低。
  2. 由于分组不是非常多,只有10000个,所以可以选择使用递归的方法,用上索引取TOP 10,速度非常快。
  3. 目前PostgreSQL的递归语法不支持递归的启动表写在subquery里面,也不支持启动表在递归查询中使用order by,所以不能直接使用递归得出结果,目前需要套一层函数。
时间: 2024-08-01 20:50:23

PgSQL · 实战经验 · 分组TOP性能提升44倍的相关文章

PostgreSQL雕虫小技,分组TOP性能提升44倍

业务背景 按分组取出TOP值,是非常常见的业务需求.比如提取每位歌手的下载量TOP 10的曲目.提取每个城市纳税前10的人或企业. 传统方法 传统的方法是使用窗口查询,PostgreSQL是支持窗口查询的.例子测试表和测试数据,生成10000个分组,1000万条记录. postgres=# create table tbl(c1 int, c2 int, c3 int); CREATE TABLE postgres=# create index idx1 on tbl(c1,c2); CREAT

刘宏程:紫光华山全新Gen9引入永久性内存 存储性能提升34倍

近日,HPE更新了自己的第9代ProLiant服务器产品组合,引入了英特尔最新的Broadwell处理器以及新的永久内存技术,可让服务器的内存作为一个高性能的存储层. 现在的HPE服务器品牌已经隶属于紫光华山旗下,此次产品更新也是蜕变后HPE的首次服务器发布.HPE升级的产品多达20款,同时也是所有服务器升级厂商中涉及型号最多的,应用产品覆盖最广的品牌. 20款服务器升级至E5 V4 紫光华山科技有限公司(原中国惠普有限公司企业集团)工业标准服务器事业部产品市场总监刘宏程表示,此次升级除处理器之

阿里云SSD云盘第二轮公测 性能提升20倍

本文讲的是阿里云SSD云盘第二轮公测 性能提升20倍6月9日,阿里云开启了"大杀器"SSD云盘的第二轮公测,其IOPS提升到了20000,是当前云盘性能的20倍.同时,盘内数据全部实时落盘,可靠性9个9.尤其适合中大型关系数据库.核心业务系统以及中大型开发测试环境使用.SSD云盘已在杭州地域公测,公测期至7月15日免费使用. IOPS(Input/Output Per Second)即每秒的输入输出量(或读写次数),是衡量磁盘性能的主要指标之一,一个普通的7200转的家用磁盘的IOPS

哈佛/MIT学生创造GPU数据库,性能提升70倍

2012年Todd Mostak 在哈佛中东研究中心攻读硕士研究生,他试图为自己的论文主题绘制和处理阿拉伯之春期间发布的4千万Twitter帖子,但处理这些帖子数小时耗费了甚至数 天.没有现成的系统能帮助他实现大数据集快速互动分析,于是他一边读MIT的数据库课程,一边开发出了利用GeForce Titan GPU处理数据的并行数据库系统MapD(演示,非论文).基于GFU的数据库系统比基于CPU的数据库性能提升了70倍,而整个硬件只花了5千美元.Todd Mostak计划在开源许可证下发布Map

Nvidia称Tegra3图形性能提升3倍

新浪科技讯 北京时间11月9日上午消息,Nvidia今天宣布,该公司的新款Tegra 3处理器图形性能比上一代产品提升3倍,而能耗则降低61%. Tegra 3此前的开发代号为"Project Kal-El",这款四核处理器目前已经投产,并将成为Nvidia在增长迅猛的移动市场投下的最新赌注.以iPad为代表的平板电脑已经开始蚕食PC销量. Nvidia最初是一家PC显示芯片生产商,其目前的多数收入仍然来自这项业务.该公司CEO黄仁勋今年9月预计,采用Tegra 3处理器的设备将于年底

PgSQL · 实战经验 · 如何预测Freeze IO风暴

背景和原理 有没有被突发的IO惊到过,有没有见到过大量的autovacuum for prevent wrap.  PostgreSQL 的版本冻结是一个比较蛋疼的事情,为什么要做版本冻结呢?  因为PG的版本号是uint32的,是重复使用的,所以每隔大约20亿个事务后,必须要冻结,否则记录会变成未来的,对当前事务"不可见".  冻结的事务号是2 src/include/access/transam.h #define InvalidTransactionId ((Transactio

优化临时表使用,SQL语句性能提升100倍

原载UC技术博客: http://tech.uc.cn/?p=2218 [问题现象] 线上mysql数据库爆出一个慢查询,DBA观察发现,查询时服务器IO飙升,IO占用率达到100%, 执行时间长达7s左右. SQL语句如下: SELECT DISTINCT g.*, cp.name AS cp_name, c.name AS category_name, t.name AS type_name FROMgm_game g LEFT JOIN gm_cp cp ON cp.id = g.cp_i

NGINX引入线程池 性能提升9倍

1. 引言 正如我们所知,NGINX采用了异步.事件驱动的方法来处理连接.这种处理方式无需(像使用传统架构的服务器一样)为每个请求创建额外的专用进程或者线程,而是在一个工作进程中处理多个连接和请求.为此,NGINX工作在非阻塞的socket模式下,并使用了epoll 和 kqueue这样有效的方法. 因为满负载进程的数量很少(通常每核CPU只有一个)而且恒定,所以任务切换只消耗很少的内存,而且不会浪费CPU周期.通过NGINX本身的实例,这种方法的优点已经为众人所知.NGINX可以非常好地处理百

成人网站性能提升20倍之经验谈

色情业是个大行业.互联网上没有多少网站的流量能和最大的色情网站相匹敌. 要搞定这巨大的流量很难.更困难的是,在色情网站上提供的很多内容都是低延迟的实时流媒体而不是简单的静态视频.但是对于所有碰到过的挑战,我很少看到有搞定过它们的开发人员写的东西.所以我决定把自己在这方面的经验写出来. 问题是什么? 几年前,我正在为当时全世界访问量排名26的网站工作 - 这里不是说的色情网站排名,而是全世界排名. 当时,该网站通过RTMP(Real Time Messaging protocol)协议响应对色情流