PostgreSQL cluster大幅减少nestloop离散IO的优化方法

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 背景 对于较大数据量的表,如果在索引字段上面有小结果集JOIN,用nestloop JOIN是比较好的方法。 但是nestloop带来的一个问题就是离散IO,这个是无法回避的问题,特别是硬件IO能力不行的情况下,性能会比较糟糕。 有什么优化方法呢? PostgreSQL提供了一个

背景

对于较大数据量的表,如果在索引字段上面有小结果集JOIN,用nestloop JOIN是比较好的方法。

但是nestloop带来的一个问题就是离散IO,这个是无法回避的问题,特别是硬件IO能力不行的情况下,性能会比较糟糕。

有什么优化方法呢?

PostgreSQL提供了一个命令,可以修改物理存储的顺序,减少离散IO就靠它了。

例子

创建两张表

postgres=# create unlogged table test01(id int primary key, info text);
CREATE TABLE
postgres=# create unlogged table test02(id int primary key, info text);
CREATE TABLE

产生一些离散primary key数据

postgres=# insert into test01 select trunc(random()*10000000), md5(random()::text) from generate_series(1,10000000) on conflict on constraint test01_pkey do nothing;
INSERT 0 6322422

postgres=#  insert into test02 select trunc(random()*10000000), md5(random()::text) from generate_series(1,10000000) on conflict on constraint test02_pkey do nothing;
INSERT 0 6320836

分析表

postgres=# analyze test01;
postgres=# analyze test02;

清除缓存,并重启

$ pg_ctl stop -m fast
# echo 3 > /proc/sys/vm/drop_caches
$ pg_ctl start

第一次调用,耗费大量的离散IO,执行时间18.490毫秒(我这台机器是SSD,IOPS能力算好的,差的机器时间更长)

postgres=# explain (analyze,verbose,timing,costs,buffers) select t1.*,t2.* from test01 t1,test02 t2 where t1.id=t2.id and t1.id between 1 and 1000;
                                                              QUERY PLAN                                                               
---------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=19.25..7532.97 rows=623 width=74) (actual time=0.465..17.221 rows=402 loops=1)
   Output: t1.id, t1.info, t2.id, t2.info
   Buffers: shared hit=1929 read=1039 dirtied=188
   ->  Bitmap Heap Scan on public.test01 t1  (cost=18.82..2306.39 rows=623 width=37) (actual time=0.416..8.019 rows=640 loops=1)
         Output: t1.id, t1.info
         Recheck Cond: ((t1.id >= 1) AND (t1.id <= 1000))
         Heap Blocks: exact=637
         Buffers: shared hit=5 read=637 dirtied=123
         ->  Bitmap Index Scan on test01_pkey  (cost=0.00..18.66 rows=623 width=0) (actual time=0.254..0.254 rows=640 loops=1)
               Index Cond: ((t1.id >= 1) AND (t1.id <= 1000))
               Buffers: shared hit=4 read=1
   ->  Index Scan using test02_pkey on public.test02 t2  (cost=0.43..8.38 rows=1 width=37) (actual time=0.013..0.013 rows=1 loops=640)
         Output: t2.id, t2.info
         Index Cond: (t2.id = t1.id)
         Buffers: shared hit=1924 read=402 dirtied=65
 Planning time: 26.668 ms
 Execution time: 18.490 ms
(17 rows)

第二次,缓存命中5.4毫秒

postgres=# explain (analyze,verbose,timing,costs,buffers) select t1.*,t2.* from test01 t1,test02 t2 where t1.id=t2.id and t1.id between 1 and 1000;
                                                              QUERY PLAN                                                               
---------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=19.25..7532.97 rows=623 width=74) (actual time=0.392..5.150 rows=402 loops=1)
   Output: t1.id, t1.info, t2.id, t2.info
   Buffers: shared hit=2968
   ->  Bitmap Heap Scan on public.test01 t1  (cost=18.82..2306.39 rows=623 width=37) (actual time=0.373..1.760 rows=640 loops=1)
         Output: t1.id, t1.info
         Recheck Cond: ((t1.id >= 1) AND (t1.id <= 1000))
         Heap Blocks: exact=637
         Buffers: shared hit=642
         ->  Bitmap Index Scan on test01_pkey  (cost=0.00..18.66 rows=623 width=0) (actual time=0.218..0.218 rows=640 loops=1)
               Index Cond: ((t1.id >= 1) AND (t1.id <= 1000))
               Buffers: shared hit=5
   ->  Index Scan using test02_pkey on public.test02 t2  (cost=0.43..8.38 rows=1 width=37) (actual time=0.004..0.004 rows=1 loops=640)
         Output: t2.id, t2.info
         Index Cond: (t2.id = t1.id)
         Buffers: shared hit=2326
 Planning time: 0.956 ms
 Execution time: 5.434 ms
(17 rows)

根据索引字段调整表的物理顺序,降低离散IO。

postgres=# cluster test01 using test01_pkey;
CLUSTER
postgres=# cluster test02 using test02_pkey;
CLUSTER
postgres=# analyze test01;
postgres=# analyze test02;

清除缓存,重启数据库

$ pg_ctl stop -m fast
# echo 3 > /proc/sys/vm/drop_caches
$ pg_ctl start

第一次调用,降低到了5.4毫秒

postgres=# explain (analyze,verbose,timing,costs,buffers) select t1.*,t2.* from test01 t1,test02 t2 where t1.id=t2.id and t1.id between 1 and 1000;
                                                                QUERY PLAN                                                                
------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.86..5618.07 rows=668 width=74) (actual time=0.069..4.072 rows=402 loops=1)
   Output: t1.id, t1.info, t2.id, t2.info
   Buffers: shared hit=2323 read=12
   ->  Index Scan using test01_pkey on public.test01 t1  (cost=0.43..30.79 rows=668 width=37) (actual time=0.040..0.557 rows=640 loops=1)
         Output: t1.id, t1.info
         Index Cond: ((t1.id >= 1) AND (t1.id <= 1000))
         Buffers: shared hit=5 read=6
   ->  Index Scan using test02_pkey on public.test02 t2  (cost=0.43..8.35 rows=1 width=37) (actual time=0.004..0.004 rows=1 loops=640)
         Output: t2.id, t2.info
         Index Cond: (t2.id = t1.id)
         Buffers: shared hit=2318 read=6  --  注意在cluster之后,shared hit并没有下降,因为LOOP了多次,但是性能确比cluster 之前提升了很多,因为需要访问的HEAP page少了,OS cache可以瞬间命中。 
 Planning time: 42.356 ms
 Execution time: 5.426 ms
(13 rows)

第二次调用,3.6毫秒

postgres=# explain (analyze,verbose,timing,costs,buffers) select t1.*,t2.* from test01 t1,test02 t2 where t1.id=t2.id and t1.id between 1 and 1000;
                                                                QUERY PLAN                                                                
------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.86..5618.07 rows=668 width=74) (actual time=0.055..3.414 rows=402 loops=1)
   Output: t1.id, t1.info, t2.id, t2.info
   Buffers: shared hit=2335
   ->  Index Scan using test01_pkey on public.test01 t1  (cost=0.43..30.79 rows=668 width=37) (actual time=0.037..0.374 rows=640 loops=1)
         Output: t1.id, t1.info
         Index Cond: ((t1.id >= 1) AND (t1.id <= 1000))
         Buffers: shared hit=11
   ->  Index Scan using test02_pkey on public.test02 t2  (cost=0.43..8.35 rows=1 width=37) (actual time=0.003..0.004 rows=1 loops=640)
         Output: t2.id, t2.info
         Index Cond: (t2.id = t1.id)
         Buffers: shared hit=2324
 Planning time: 1.042 ms
 Execution time: 3.620 ms
(13 rows)

小结

通过cluster, 将表的物理顺序和索引对齐,所以如果查询的值是连续的,在使用嵌套循环时可以大幅减少离散IO,取得非常好查询优化的效果。

如果查询的值是跳跃的,那么这种方法就没有效果啦,不过好在PostgreSQL有bitmap index scan,在读取heap tuple前,会对ctid排序,按排序后的ctid取heap tuple,也可以起到减少离散IO的作用。

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
7月前
|
监控 关系型数据库 数据库
PostgreSQL的索引优化策略?
【8月更文挑战第26天】PostgreSQL的索引优化策略?
169 1
|
5月前
|
人工智能 Cloud Native Java
云原生技术深度解析:从IO优化到AI处理
【10月更文挑战第24天】在当今数字化时代,云计算已经成为企业IT架构的核心。云原生作为云计算的最新演进形态,旨在通过一系列先进的技术和实践,帮助企业构建高效、弹性、可观测的应用系统。本文将从IO优化、key问题解决、多线程意义以及AI处理等多个维度,深入探讨云原生技术的内涵与外延,并结合Java和AI技术给出相应的示例。
171 1
|
6月前
|
缓存 关系型数据库 数据库
如何优化 PostgreSQL 数据库性能?
如何优化 PostgreSQL 数据库性能?
291 2
|
7月前
|
SQL 关系型数据库 Linux
在CentOS 6上安装和使用PostgreSQL的方法
在CentOS 6上安装和使用PostgreSQL的方法
93 2
|
7月前
|
Ubuntu 关系型数据库 数据库
在Ubuntu 18.04上安装和使用PostgreSQL的方法
在Ubuntu 18.04上安装和使用PostgreSQL的方法
115 1
|
7月前
|
Ubuntu 关系型数据库 Linux
在Ubuntu 14.04上安装和使用PostgreSQL的方法
在Ubuntu 14.04上安装和使用PostgreSQL的方法
71 1
|
7月前
|
监控 关系型数据库 数据库
如何优化PostgreSQL的性能?
【8月更文挑战第4天】如何优化PostgreSQL的性能?
336 7
|
7月前
|
缓存 Linux 调度
[kvm]硬盘IO优化
[kvm]硬盘IO优化
|
7月前
|
关系型数据库 Linux 数据库
在CentOS 7上安装和使用PostgreSQL的方法
在CentOS 7上安装和使用PostgreSQL的方法
584 0
|
9月前
|
Java 开发者
Java IO流实战技巧:如何优化InputStream/OutputStream和Reader/Writer的使用?
【6月更文挑战第26天】Java IO流优化涉及缓冲、资源管理、字符编码和流式处理。使用Buffered流提高读写效率,如`BufferedInputStream`和`BufferedReader`。确保资源关闭使用try-with-resources,如`try (InputStream is = ...) {...}`。处理文本时指定编码,如`InputStreamReader(is, StandardCharsets.UTF_8)`防止乱码。流式处理大文件,分块读写避免内存溢出,以减少内存占用。这些技巧能提升程序性能和健壮性。
324 0

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版