热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

背景对于较大数据量的表,如果在索引字段上面有小结果集JOIN,用nestloopJOIN是比较好的方法。但是nestloop带来的一个问题就是离散IO&

背景

对于较大数据量的表,如果在索引字段上面有小结果集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 6322422postgres=# 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&#61;19.25..7532.97 rows&#61;623 width&#61;74) (actual time&#61;0.465..17.221 rows&#61;402 loops&#61;1)Output: t1.id, t1.info, t2.id, t2.infoBuffers: shared hit&#61;1929 read&#61;1039 dirtied&#61;188-> Bitmap Heap Scan on public.test01 t1 (cost&#61;18.82..2306.39 rows&#61;623 width&#61;37) (actual time&#61;0.416..8.019 rows&#61;640 loops&#61;1)Output: t1.id, t1.infoRecheck Cond: ((t1.id >&#61; 1) AND (t1.id <&#61; 1000))Heap Blocks: exact&#61;637Buffers: shared hit&#61;5 read&#61;637 dirtied&#61;123-> Bitmap Index Scan on test01_pkey (cost&#61;0.00..18.66 rows&#61;623 width&#61;0) (actual time&#61;0.254..0.254 rows&#61;640 loops&#61;1)Index Cond: ((t1.id >&#61; 1) AND (t1.id <&#61; 1000))Buffers: shared hit&#61;4 read&#61;1-> Index Scan using test02_pkey on public.test02 t2 (cost&#61;0.43..8.38 rows&#61;1 width&#61;37) (actual time&#61;0.013..0.013 rows&#61;1 loops&#61;640)Output: t2.id, t2.infoIndex Cond: (t2.id &#61; t1.id)Buffers: shared hit&#61;1924 read&#61;402 dirtied&#61;65Planning time: 26.668 msExecution time: 18.490 ms
(17 rows)

第二次&#xff0c;缓存命中5.4毫秒

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

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

postgres&#61;# cluster test01 using test01_pkey;
CLUSTER
postgres&#61;# cluster test02 using test02_pkey;
CLUSTER
postgres&#61;# analyze test01;
postgres&#61;# analyze test02;

清除缓存&#xff0c;重启数据库

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

第一次调用&#xff0c;降低到了5.4毫秒

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

第二次调用&#xff0c;3.6毫秒

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

小结

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

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



推荐阅读
  • 本文介绍了在PostgreSQL中批量导入数据时的优化方法。包括使用unlogged表、删除重建索引、删除重建外键、禁用触发器、使用COPY方法、批量插入等。同时还提到了一些参数优化的注意事项,如设置effective_cache_size、shared_buffer等,并强调了在导入大量数据后使用analyze命令重新收集统计信息的重要性。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • PDO MySQL
    PDOMySQL如果文章有成千上万篇,该怎样保存?数据保存有多种方式,比如单机文件、单机数据库(SQLite)、网络数据库(MySQL、MariaDB)等等。根据项目来选择,做We ... [详细]
  • MySQL多表数据库操作方法及子查询详解
    本文详细介绍了MySQL数据库的多表操作方法,包括增删改和单表查询,同时还解释了子查询的概念和用法。文章通过示例和步骤说明了如何进行数据的插入、删除和更新操作,以及如何执行单表查询和使用聚合函数进行统计。对于需要对MySQL数据库进行操作的读者来说,本文是一个非常实用的参考资料。 ... [详细]
  • 本文总结和分析了JDK核心源码(2)中lang包下的基础知识,包括常用的对象类型包和异常类型包。在对象类型包中,介绍了Object类、String类、StringBuilder类、StringBuffer类和基本元素的包装类。在异常类型包中,介绍了Throwable类、Error类型和Exception类型。这些基础知识对于理解和使用JDK核心源码具有重要意义。 ... [详细]
  • 基于分布式锁的防止重复请求解决方案
    一、前言关于重复请求,指的是我们服务端接收到很短的时间内的多个相同内容的重复请求。而这样的重复请求如果是幂等的(每次请求的结果都相同,如查 ... [详细]
  • Yii framwork 应用小窍门
    Yiiframework应用小窍门1.YiiFramework]如何获取当前controller的名称?下面语句就可以获取当前控制器的名称了!Php代码 ... [详细]
  • 【CTF 攻略】第三届 SSCTF 全国网络安全大赛—线上赛 Writeup
    【CTF 攻略】第三届 SSCTF 全国网络安全大赛—线上赛 Writeup ... [详细]
  • 开发笔记:对称加密详解,以及JAVA简单实现
     (原)常用的加密有3种1、正向加密,如MD5,加密后密文固定,目前还没办法破解,但是可以能过数据库撞库有一定概率找到,不过现 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
author-avatar
正在减肥的小小_519
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有