暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

伪SQL优化大师速成法

文章转载自公众号:PostgreSQL学徒

作者:熊灿灿

前言

SQL优化是一个任重而道远的活,有时线上可能仅仅是一条小小的不起眼SQL突然跑慢了,便可能引发业务系统雪崩,所以SQL优化的重要性不言而喻,但是,不少DBA或者开发都认为SQL优化很简单,加索引。

遗憾的是,SQL优化是一项十分复杂的技术栈,不仅仅是:

  1. analyze简单地收集一下统计信息

  2. 根据where列、order by列等创建索引

  3. 使用pg_hint_plan或者pg_plan_advsr建议一下优化器

  4. 使用prepareStatements绑定一下执行计划减少解析时间

  5. 无脑让开发降低并发,无脑scale out或者scale up,减少连接表的个数等

核心还要学会SQL的等价改写,但是这需要经历一个漫长的经验累积的过程,以我目前的功力还远远达不到去对SQL等价改写这个复杂的技术栈去评头论足,不过既然高级一点的不会,那我们就旁路,迂回战术,使用一些现有的工具,如pgMustard — review Postgres query plans quickly,pganalyze自动化性能诊断和优化产品等,助力我们优化SQL,达到事半功倍的效果。

今天分享的是PostgreSQL中,协助我们分析SQL的工具。

Depesz’ EXPLAIN ANALYZE visualizer

这个工具是Hubert Lubaczewski大师的杰作,地址在:https://explain.depesz.com/,使用很简单,先造个数据,模拟一下多表join

postgres=# create table t1(  postgres(#   id int primary key,  postgres(#   info text default 'tessssssssssssssssssssssssssssssssssssst',  postgres(#   state int default 0,  postgres(#   crt_time timestamp default now(),  postgres(#   mod_time timestamp default now()  postgres(# );CREATE TABLEpostgres=# create table t2 (like t1 including all);  CREATE TABLEpostgres=# create table t3 (like t1 including all);CREATE TABLEpostgres=# insert into t1 select generate_series(1,1000000);  INSERT 0 1000000postgres=# insert into t2 select * from t1;  INSERT 0 1000000postgres=# insert into t3 select * from t1;INSERT 0 1000000postgres=# explain select * from t1 join t2 using (id) join t3 using (id) where t1.id= random();                                QUERY PLAN                                ----------------------------------------------------------------------------Nested Loop  (cost=29908.93..57468.83 rows=5000 width=178)  Join Filter: (t1.id = t3.id)  -> Hash Join  (cost=29908.50..54879.51 rows=5000 width=130)        Hash Cond: (t2.id = t1.id)        -> Seq Scan on t2  (cost=0.00..22346.00 rows=1000000 width=65)        -> Hash  (cost=29846.00..29846.00 rows=5000 width=65)              -> Seq Scan on t1  (cost=0.00..29846.00 rows=5000 width=65)                    Filter: ((id)::double precision = random())  -> Index Scan using t3_pkey on t3  (cost=0.43..0.51 rows=1 width=56)        Index Cond: (id = t2.id)(10 rows)

使用很简单,分别上传explain analyze + SQL的真实执行计划情况和对应的SQL

然后submit提交即可,就会基于当前的执行计划生成一份HTML的报告

当然,还可以加上一些附加的explain选项,如buffers查看缓冲区的使用情况,这样会更加方便我们分析

postgres=# explain (analyze,buffers) select * from t1 join t2 using (id) join t3 using (id) where t1.id= random();

这里简单回顾一下执行计划:

  1. explain命令的输出结果中每个cost就是该执行节点的代价估计。它的格式是xxx..xxx,在...之前的是预估的启动代价,即找到符合该节点条件的第一个结果预估所需要的代价,在...之后的是预估的总代价。而父节点的启动代价包含子节点的总代价。

  2. actual time执行时间,格式为xxx...xxx,在...之前的是该节点实际的启动时间,即找到符合该节点条件的第一个结果实际需要的时间,在...之后的是该节点实际的执行时间

  3. rows指的是该节点实际的返回行数

  4. loops指的是该节点实际的重启次数。如果一个计划节点在运行过程中,它的相关参数值(如绑定变量)发生了变化,就需要重新运行这个计划节点。

执行计划看起来与原有的执行计划有点相似,但从外观上看起来更加清晰。还有一些有用的附加特性:

  1. 每个节点的总执行时间和净执行时间,耗时最高的节点用红色背景高亮显示,是我们的重点观察对象

  2. inclusive:包含启动的总执行时间,会以由浅到深的颜色标识,对于特别大的会高亮显示,提醒我们这一块是优化重点

  3. exclusive:较下层Node增长了多少时间,会以由浅到深的颜色标识,对于增长特别多的会高亮显示,提醒我们这一块是优化重点

  4. rows x:这一列可以帮我们协助分析是什么因素让PostgreSQL错误地高估或低估了返回的行数。错误的估算会用红色背景高亮,此例中我们可以看到实际返回了1行,但是预估的行数是1000000,100万,可以看到,此时优化器严重估算错误 (实际原因是因为等于条件为random())

  5. rows:就是实际的返回行数,以及通过过滤条件过滤了多少行,比如第5行,过滤了- 1000000行,实际返回了0行,说明选择率极高,可以考虑建合适的索引,比如万能Btree,适用于数组、多列的倒排Gin、时序数据Brin等

  6. loops:就是实际的循环重启次数

当然还有一些其他有趣的特性,我将鼠标放至顶层Node节点,会将子节点用⭐标记出来,这会很方便我们看下层Node节点

点击一下上层Node节点,会将子节点隐藏起来,对于特别长特别复杂的执行计划,会很方便我们分析SQL

最后一个stats列则是SQL的总览信息

这里我们可以看到,我们的SQL实际跑起来,和统计信息的差距相差很多,因为我们的条件是等于random(),换成一个具体的值再看一下:

postgres=# explain (analyze,buffers) select * from t1 join t2 using (id) join t3 using (id) where t1.id= 5;                                                      QUERY PLAN                 -------------------------------------------------------------------------------------------------------------------------Nested Loop  (cost=1.27..25.35 rows=1 width=187) (actual time=0.656..0.661 rows=1 loops=1)  Buffers: shared hit=11 read=1  -> Nested Loop  (cost=0.85..16.90 rows=1 width=126) (actual time=0.641..0.643 rows=1 loops=1)        Buffers: shared hit=7 read=1        -> Index Scan using t1_pkey on t1  (cost=0.42..8.44 rows=1 width=65) (actual time=0.613..0.614 rows=1 loops=1)              Index Cond: (id = 5)              Buffers: shared hit=3 read=1        -> Index Scan using t2_pkey on t2  (cost=0.42..8.44 rows=1 width=65) (actual time=0.021..0.022 rows=1 loops=1)              Index Cond: (id = 5)              Buffers: shared hit=4  -> Index Scan using t3_pkey on t3  (cost=0.42..8.44 rows=1 width=65) (actual time=0.012..0.013 rows=1 loops=1)        Index Cond: (id = 5)        Buffers: shared hit=4Planning Time: 0.206 msExecution Time: 0.730 ms(15 rows)

Dalibo’s EXPLAIN ANALYZE visualizer

又名大力波,网址:https://explain.dalibo.com/,大力波官方建议我们使用

For best results, use EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON)

我们继续用原来的SQL看一下效果,和Depesz的是两种不同风格,左边是一个总览Overview,右边是具体的细节Details

点开即可看到Details,有一个很贴心的地方在于,大力波会把Scan Node的意思告诉你,所以对于看不懂Node的,通过这里也可以看出一二

  • Seq Scan Node finds relevant records by sequentially scanning the input record set. When reading from a table, Seq Scans (unlike Index Scans) perform a single read operation (only the table is read).

  • Index Scan Node finds relevant records based on an Index. Index Scans perform 2 read operations: one to read the index and another to read the actual value from the table.

  • Nested Loop Node merges two record sets by looping through every record in the first set and trying to find a match in the second set. All matching records are returned.

  • Hash Join Node joins two record sets by hashing one of them (using a Hash Scan).

这种方式假如觉得抽象不易阅读的话,可以转化为传统风格,通过右上角的settings设置classic:

通过Node的Details,我们可以获取到如下信息,以Seq Scan为例:

  1. 预估严重偏差,优化评估结果较真实结果多了5000行

  2. 过滤了99%的行,意味着选择率极高,可以考虑建索引

  3. 时间占用总SQL 100%的时间,也就是单条SQL基本全部耗时花在了这里,那么是我们重点观察的对象

  4. 成本较高29800,颜色是较高级别橙色

  5. buffers命中了1.5MB,读取了95MB,另外还会显示出Dirtyed,比如shared_buffer太小了,bgwriter进程没有维护好足够多的buffer,那么backend process得自己去找合适的block,假如更不巧还是脏块的话,还得做刷脏的累活

左侧是可选的输出什么,比如输出时间还是输出cost

同理,还有一个总览stats

Tatiyants’s EXPLAIN ANALYZE visualizer

这个和大力波十分类似,可以看到slowest最慢的,costliest最昂贵的,返回结果集最大的largest的Node节点

不过有一点很棒,可以看到具体Node的SQL,如下,其余就不再赘述,和大力波类似。

pgMustard

这个是一项收费的产品,https://www.pgmustard.com/,不过可以免费试用5次

毕竟收费,功能更加高级一点,会输出相应的建议,对于新手来说,还是有点帮助的,聊胜于无

pganalyze

pganalyze应该称作是一项解决方案,监控、调优、告警等,都囊括其中了。在此就不做演示了,感兴趣的可以自行尝试,有15天的试用期。

我们可以当作是一个很好的学习案例,每个Node都会有解释:

https://pganalyze.com/docs/explain/other-nodes/set-op

除此之外,pganalyze比较体系化,可以通过阅读文档,了解该产品的思想,以及对各种瓶颈的分析和建议。

  • Postgres Query Analysis & Postgres Explain Plans

  • Discover the root cause of critical issues, optimize slow queries, and find missing indices

规模空前,再创历史 | 2020 PG亚洲大会圆满结束
PG ACE计划的正式发布
三期PostgreSQL国际线上沙龙活动的举办
六期PostgreSQL国内线上沙龙活动的举办

中国PostgreSQL分会与腾讯云战略合作协议签订


PostgreSQL 13.0 正式版发布通告

深度报告:开源协议那些事儿

从“非主流”到“潮流”,开源早已值得拥有

Oracle中国正在进行新一轮裁员,传 N+6 补偿

PostgreSQL与MySQL版权比较

新闻|Babelfish使PostgreSQL直接兼容SQL Server应用程序

四年三冠,PostgreSQL再度荣获“年度数据库”


更多新闻资讯行业动态技术热点,请关注中国PostgreSQL分会官方网站

https://www.postgresqlchina.com

中国PostgreSQL分会生态产品

https://www.pgfans.cn

中国PostgreSQL分会资源下载站

https://www.postgreshub.cn


点击此处阅读原文

↓↓↓

文章转载自开源软件联盟PostgreSQL分会,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论