PostgreSQL常用优化技巧示例介绍

目录
  • 1、标量子查询与filter
  • 2、视图合并
  • 3、谓词推入

1、标量子查询与filter

当一个查询在select和from之间,那么这种子查询就是标量子查询。实际应用中,很多人在写SQL时为了方便会写一堆标量子查询的SQL,在表数据不大时,一般并不会有什么影响,但当数据量较大时,往往会对性能造成巨大影响。

因为标量子查询类似于一个天然的嵌套循环,而且驱动表固定为主表。如下所示:

bill=# explain select empno,ename,sal,deptno,
bill-# (select d.dname from dept d where d.deptno = e.deptno) as dname
bill-# from emp e;
                          QUERY PLAN
--------------------------------------------------------------
 Seq Scan on emp e  (cost=0.00..15.84 rows=14 width=64)
   SubPlan 1
     ->  Seq Scan on dept d  (cost=0.00..1.05 rows=1 width=9)
           Filter: (deptno = e.deptno)
(4 rows)

对于上面的SQL,emp表每输出一行数据,都要去dept表中全表扫描一遍。

而我们都知道,嵌套循环的被驱动表的连接列必须包含在索引中,同理,标量子查询的表的连接列也必须包含在索引中。但是我们在实际写SQL时还是要避免使用标量子查询,否则主表返回大量数据时,子表得被多次遍历,从而对SQL性能产生巨大影响。

那么对于标量子查询的SQL我们该怎么优化呢?最常用的就是改写成外连接,这样对于PostgreSQL的优化器而言可以根据实际情况去选择表的连接方式。这里需要注意的是,不能将标量子查询改成内连接,我们前面的例子中也可以看到,标量子查询实际是一个传值的过程,当主表传值给子表时,如果没有相应的值则会显示NULL,而如果使用内连接的话这部分数据就丢失了。

因此,上面的标量子查询可以改写成:

可以看到,优化器根据实际情况选择了更合适的hash join。

bill=# explain select e.empno,e.ename,e.sal,e.deptno,d.dname
bill-# from emp e
bill-# left join dept d on (d.deptno = e.deptno);
                            QUERY PLAN
-------------------------------------------------------------------
 Hash Left Join  (cost=1.09..2.31 rows=14 width=27)
   Hash Cond: (e.deptno = d.deptno)
   ->  Seq Scan on emp e  (cost=0.00..1.14 rows=14 width=18)
   ->  Hash  (cost=1.04..1.04 rows=4 width=13)
         ->  Seq Scan on dept d  (cost=0.00..1.04 rows=4 width=13)
(5 rows)

当主表连接列是外键,而子表的连接列是主键时,使用内连接也可以,因为外键自然不会存在NULL值。

bill=# explain select e.empno,e.ename,e.sal,e.deptno,d.dname
bill-# from emp e
bill-# inner join dept d on (d.deptno = e.deptno);
                            QUERY PLAN
-------------------------------------------------------------------
 Hash Join  (cost=1.09..2.31 rows=14 width=27)
   Hash Cond: (e.deptno = d.deptno)
   ->  Seq Scan on emp e  (cost=0.00..1.14 rows=14 width=18)
   ->  Hash  (cost=1.04..1.04 rows=4 width=13)
         ->  Seq Scan on dept d  (cost=0.00..1.04 rows=4 width=13)
(5 rows)

除了标量子查询外,往往filter也会产生类似的情况,因为在filter中驱动表也会被固定住,那么优化器可能会选择低效的执行计划。而对于PostgreSQL而言本身也不支持hint功能,如果错误的执行计划被固定,那么往往只能去改写SQL。

这里说明下下filter,在PostgreSQL中filter主要有2种情况,一种是我们常见的where后面过滤数据的,这种一般不会产生什么性能问题,例如:

bill=# explain select * from t where id < 10;
                      QUERY PLAN
-------------------------------------------------------
 Seq Scan on t  (cost=0.00..16925.00 rows=100 width=4)
   Filter: (id < 10)
(2 rows)

而另一种就是filter中是一些表的连接条件,这种呢便是我们前面说的情况,往往需要去关注的,例如:

bill=# explain select  exists (select 1 from t where t.id=n.id) from n;
                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on n  (cost=0.00..169250145.00 rows=10000 width=1)
   SubPlan 1
     ->  Seq Scan on t  (cost=0.00..16925.00 rows=1 width=0)
           Filter: (id = n.id)
(4 rows)

那么哪些写法会容易产生filter呢?在PostgreSQL中当使用exists或者not exists时,或者子查询中有固话子查询的关键词,如union、union all、cube、rollup、limit等,那么执行计划往往容易产生filter。

因此上面的SQL我们用in去替换exists进行改写:

bill=# explain select id in (select id from t) from n;
                               QUERY PLAN
-------------------------------------------------------------------------
 Seq Scan on n  (cost=0.00..129160170.00 rows=10000 width=1)
   SubPlan 1
     ->  Materialize  (cost=0.00..23332.00 rows=1000000 width=4)
           ->  Seq Scan on t  (cost=0.00..14425.00 rows=1000000 width=4)
(4 rows)

除此之外,在PostgreSQL中我们更推荐使用= any的方式去改写该类SQL:

bill=# explain select id = any(array(select id from t)) from n;
                            QUERY PLAN
-------------------------------------------------------------------
 Seq Scan on n  (cost=14425.00..14695.00 rows=10000 width=1)
   InitPlan 1 (returns $0)
     ->  Seq Scan on t  (cost=0.00..14425.00 rows=1000000 width=4)
(3 rows)

当然这并不是说in的写法就一定比exists要好,只是相较于exists更不容易产生filter。这是为什么呢?因为如果子查询中包含我们上面提到的固化关键字时,子查询会被固化为一个整体,当采用exists写法时,如果子查询中有主表的连接列,那么便只能是主表通过连接列给子查询中的表传值,因此会选择filter。而使用in的写法,即使子查询被固化,但如果没有主表连接列的字段,那么便不会选择filter。

2、视图合并

不知道大家有没有遇到过类似下面的情况:

select xxx from () t1, () t2 where t1.id = t2.id;

明明t1和t2两个子查询单独执行都很快,但是放到一起速度却变得特别慢,这种情况往往就是视图合并所导致的。

例如下面的SQL:

我们按照SQL中的顺序来看应该是emp和dept两表先进行关联,然后再去和salgrade表关联。但执行计划中的顺序却变成了emp和salgrade表先关联,最后才去关联dept表。

这说明发生了视图合并,即视图/子查询中的内容被拆开了。

bill=# explain select a.*,c.grade
bill-#     from (select ename,sal,a.deptno,b.dname
bill(#         from emp a,dept b
bill(#         where a.deptno = b.deptno) a,
bill-#         salgrade c
bill-#     where a.sal between c.losal and c.hisal;
                                 QUERY PLAN
-----------------------------------------------------------------------------
 Hash Join  (cost=1.09..4.56 rows=8 width=27)
   Hash Cond: (a.deptno = b.deptno)
   ->  Nested Loop  (cost=0.00..3.43 rows=8 width=18)
         Join Filter: ((a.sal >= c.losal) AND (a.sal <= c.hisal))
         ->  Seq Scan on emp a  (cost=0.00..1.14 rows=14 width=14)
         ->  Materialize  (cost=0.00..1.07 rows=5 width=12)
               ->  Seq Scan on salgrade c  (cost=0.00..1.05 rows=5 width=12)
   ->  Hash  (cost=1.04..1.04 rows=4 width=13)
         ->  Seq Scan on dept b  (cost=0.00..1.04 rows=4 width=13)
(9 rows)

从上面的例子可以看出,视图合并一般产生性能问题都是因为发生视图合并后表的连接顺序变化导致的。不过一般优化器这么做是为了帮我们选择更合适的表连接顺序,而当优化器选择了错误的连接顺序时,我们就有必要对SQL进行改写了。

由于PostgreSQL中我们无法使用hint来让优化器禁止视图合并,所以我们便需要了解一些SQL改写的技巧。

和前面的filter一样,当我们将视图/子查询固化后,那么便不能进行视图合并。因此上面的SQL我们可以改写为:

加上group by后,子查询被固化,视图没有发生合并,emp和dept表先进行关联了。

bill=# explain select a.*,c.grade
bill-#     from (select ename,sal,a.deptno,b.dname
bill(#         from emp a,dept b
bill(#         where a.deptno = b.deptno group by ename,sal,a.deptno,b.dname) a,
bill-#         salgrade c
bill-#     where a.sal between c.losal and c.hisal;
                                  QUERY PLAN
-------------------------------------------------------------------------------
 Nested Loop  (cost=2.45..5.02 rows=8 width=27)
   Join Filter: ((a.sal >= c.losal) AND (a.sal <= c.hisal))
   ->  HashAggregate  (cost=2.45..2.59 rows=14 width=23)
         Group Key: a.ename, a.sal, a.deptno, b.dname
         ->  Hash Join  (cost=1.09..2.31 rows=14 width=23)
               Hash Cond: (a.deptno = b.deptno)
               ->  Seq Scan on emp a  (cost=0.00..1.14 rows=14 width=14)
               ->  Hash  (cost=1.04..1.04 rows=4 width=13)
                     ->  Seq Scan on dept b  (cost=0.00..1.04 rows=4 width=13)
   ->  Materialize  (cost=0.00..1.07 rows=5 width=12)
         ->  Seq Scan on salgrade c  (cost=0.00..1.05 rows=5 width=12)
(11 rows)

3、谓词推入

说完视图合并,我们再来看看你视图不能合并时会出现的一种情况——谓词推入。即对于那些不能合并的视图,并且有谓词进行过滤,CBO会将谓词过滤条件推入到视图内,为了尽早的过滤掉无用的数据,从而提升性能。

从CBO的角度来看,进行谓词推入自然是好的,因为可以提前过滤掉不需要的数据。但是如果推入的谓词是连接列的,那么可能导致表的join产生变化,SQL性能变得更差。

如下SQL所示:

外层的谓词d.deptno between c.losal and c.hisal推入到了视图里面。

bill=# create or replace view v1 as select ename,sal,a.deptno,b.dname
bill-#         from emp a,dept b
bill-#         where a.deptno = b.deptno;
CREATE VIEW
bill=# explain select d.*,c.grade from v1 d,salgrade c
bill-#     where d.deptno between c.losal and c.hisal;
                                 QUERY PLAN
-----------------------------------------------------------------------------
 Hash Join  (cost=1.09..4.56 rows=8 width=27)
   Hash Cond: (a.deptno = b.deptno)
   ->  Nested Loop  (cost=0.00..3.43 rows=8 width=18)
         Join Filter: ((a.deptno >= c.losal) AND (a.deptno <= c.hisal))
         ->  Seq Scan on emp a  (cost=0.00..1.14 rows=14 width=14)
         ->  Materialize  (cost=0.00..1.07 rows=5 width=12)
               ->  Seq Scan on salgrade c  (cost=0.00..1.05 rows=5 width=12)
   ->  Hash  (cost=1.04..1.04 rows=4 width=13)
         ->  Seq Scan on dept b  (cost=0.00..1.04 rows=4 width=13)
(9 rows)

那我们该如何防止谓词内推呢?在Oracle中可以通过关闭连接列的谓词推入的隐藏参数_push_join_predicate来实现,那么在PostgreSQL中又该如何实现呢?

和上面类似,我们可以将视图固化来避免这种情况,但一般来说不建议将视图固化,因为大部分情况谓词推入大多数对性能是有好处的。例如当我们在视图中使用limit时会导致谓词无法推入,因此一般也不建议在视图中使用limit,为什么呢?因为如果谓词进行推入的话,limit取到的值可能就不同了,会对结果集产生影响,所以自然不能推入了,因为优化器的任何等价转换都是在不改变SQL结果的情况下才能进行的。

到此这篇关于PostgreSQL常用优化技巧示例介绍的文章就介绍到这了,更多相关PostgreSQL优化技巧内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • PostgreSQL 性能优化之服务器参数配置操作

    大家好!我是只谈技术不剪发的 Tony 老师.今天我们来聊聊 PostgreSQL 的性能优化:数据库优化是一个系统的工程,本文只专注于服务器的参数配置优化. 默认安装时,PostgreSQL 的配置参数通常都偏小,不太适合作为生产服务器使用.所以,安装 PostgreSQL 数据库之后首先需要执行的操作就是对服务器的配置参数进行调整. 查看/设置参数值 我们使用 PostgreSQL 12,服务器的配置参数有 300 多个,运行时的参数值可以使用 SHOW 命令查看: show server_

  • PostgreSQL常用优化技巧示例介绍

    目录 1.标量子查询与filter 2.视图合并 3.谓词推入 1.标量子查询与filter 当一个查询在select和from之间,那么这种子查询就是标量子查询.实际应用中,很多人在写SQL时为了方便会写一堆标量子查询的SQL,在表数据不大时,一般并不会有什么影响,但当数据量较大时,往往会对性能造成巨大影响. 因为标量子查询类似于一个天然的嵌套循环,而且驱动表固定为主表.如下所示: bill=# explain select empno,ename,sal,deptno, bill-# (se

  • mysql in语句子查询效率慢的优化技巧示例

    表结构如下,文章只有690篇. 文章表article(id,title,content) 标签表tag(tid,tag_name) 标签文章中间表article_tag(id,tag_id,article_id) 其中有个标签的tid是135,查询标签tid是135的文章列表. 690篇文章,用以下的语句查询,奇慢: select id,title from article where id in( select article_id from article_tag where tag_id=

  • MySQL数据库十大优化技巧

    1.优化你的MySQL查询缓存 在MySQL服务器上进行查询,可以启用高速查询缓存.让数据库引擎在后台悄悄的处理是提高性能的最有效方法之一.当同一个查询被执行多次时,如果结果是从缓存中提取,那是相当快的. 但主要的问题是,它是那么容易被隐藏起来以至于我们大多数程序员会忽略它.在有些处理任务中,我们实际上是可以阻止查询缓存工作的. 复制代码 代码如下: // query cache does NOT work $r = mysql_query("SELECT username FROM user

  • Spring Boot优化后启动速度快到飞起技巧示例

    目录 引言 启动时间分析 启动优化 减少业务初始化 延迟初始化 Spring Context Indexer 关闭JMX 关闭分层编译 另外的思路 JAR Index APPCDS Heap Archive AOT编译 下线时间优化 优雅下线 Eureka服务下线时间 结束 引言 微服务用到一时爽,没用好就呵呵啦,特别是对于服务拆分没有把控好业务边界.拆分粒度过大等问题,某些 Spring Boot 启动速度太慢了,可能你也会有这种体验,这里将探索一下关于 Spring Boot 启动速度优化的

  • 常用SQL语句优化技巧总结【经典】

    本文实例总结了常用SQL语句优化技巧.分享给大家供大家参考,具体如下: 除了建立索引之外,保持良好的SQL语句编写习惯将会降低SQL性能问题发生. ①通过变量的方式来设置参数 好: stringsql = "select * from people p where p.id = ? "; 坏: stringsql = "select * from people p where p.id = "+id; 数据库的SQL文解析和执行计划会保存在缓存中,但是SQL文只要有

  • Android开发组件flutter的20个常用技巧示例总结

    目录 1.map遍历快速实现边距,文字自适应改变大小 2.使用SafeArea 添加边距 3.布局思路 4.获取当前屏幕的大小 5.文本溢出显示省略号 6.一个圆角带搜索icon的搜索框案例 7.修改按钮的背景色 8.tab切换实例 9.点击事件组件点击空白区域不触发点击 10.使用主题色 11.往安卓模拟器中传图片 12.控制text的最大行数显示影藏文字 13.去掉默认的抽屉图标 14.图片占满屏 15.倒计时 16.固定底部 17.添加阴影 18.隐藏键盘 19.获取父级组件大小 20.点

  • Vue.js九个性能优化技巧(值得收藏)

    目录 Functional components Child component splitting Local variables Reuse DOM with v-show KeepAlive Deferred features Time slicing Non-reactive data Virtual scrolling 总结 参考资料 这篇文章主要参考了 Vue.js 核心成员Guillaume Chau在 19 年美国的 Vue conf 分享的主题:9 Performance se

  • Asp编码优化技巧

    8条Asp编码优化技巧: 1.声明VBScript变量      在ASP中,对vbscript提供了强劲的支持,能够无缝集成vbscript的函数.方法,这样给扩展ASP的现有功能提供了很大便利.由于ASP中已经模糊了变量类型的概念,所以,在进行ASP与vbscript交互的过程中,很多程序员也惯于不声明vbscript的变量,这样加重了服务器的解析负担,进而影响服务器的响应请求速度. 鉴于此,我们可以象在VB中强制用户进行变量声明一样在vbscript中强制用户进行变量声明.实现方法是在AS

  • 深入学习SQL Server聚合函数算法优化技巧

    Sql server聚合函数在实际工作中应对各种需求使用的还是很广泛的,对于聚合函数的优化自然也就成为了一个重点,一个程序优化的好不好直接决定了这个程序的声明周期.Sql server聚合函数对一组值执行计算并返回单一的值.聚合函数对一组值执行计算,并返回单个值.除了 COUNT 以外,聚合函数都会忽略空值. 聚合函数经常与 SELECT 语句的 GROUP BY 子句一起使用. 一.写在前面 如果有对Sql server聚合函数不熟或者忘记了的可以看我之前的一片博客. 本文中所有数据演示都是用

  • 简单了解MySQL数据库优化技巧

    一个成熟的数据库架构并不是一开始设计就具备高可用.高伸缩等特性的,它是随着用户量的增加,基础架构才逐渐完善.这篇文章主要谈谈MySQL数据库在发展周期中所面临的问题及优化方案,暂且抛开前端应用不说,大致分为以下五个阶段: 阶段一:数据库表设计 项目立项后,开发部门根据产品部门需求开发项目. 开发工程师在开发项目初期会对表结构设计.对于数据库来说,表结构设计很重要,如果设计不当,会直接影响到用户访问网站速度,用户体验不好!这种情况具体影响因素有很多,例如慢查询(低效的查询语句).没有适当建立索引.

随机推荐