提高 Postgresql 查询性能

Posted

技术标签:

【中文标题】提高 Postgresql 查询性能【英文标题】:Improve Postgre SQL query performance 【发布时间】:2021-08-03 21:23:27 【问题描述】:

我正在我们的数据库中运行此查询:

select
(
  select least(2147483647, sum(pb.nr_size)) 
  from tb_pr_dc pd
  inner join tb_pr_dc_bn pb on 1=1
    and pb.id_pr_dc_bn = pd.id_pr_dc_bn 
  where 1=1
    and pd.id_pr = pt.id_pr -- outer query column
) 
from 
(
  select regexp_split_to_table('[list of 500 ids]', ',')::integer id_pr 
) pt 
;

它输出 500 行,只有一个结果列,运行大约需要 1 分 43 秒。 explain (analyze, verbose, buffers) 输出以下计划:

子查询扫描 pt (cost=0.00..805828.19 rows=1000 width=8) (实际时间=96.791..103205.872 rows=500 loops=1) 输出:(子计划 1) 缓冲区:共享命中=373771 读取=153484 -> 结果(成本=0.00..22.52 行=1000 宽度=4)(实际时间=0.434..3.729 行=500 循环=1) 输出: ((regexp_split_to_table('[list of 500 ids]', ',')::integer id_pr) -> ProjectSet(成本=0.00..5.02 行=1000 宽度=32)(实际时间=0.429..2.288 行=500 循环=1) 输出: (regexp_split_to_table('[list of 500 ids]', ',')::integer id_pr -> 结果(成本=0.00..0.01 行=1 宽度=0)(实际时间=0.001..0.001 行=1 循环=1) 子计划 1 -> 聚合(成本=805.78..805.80 行=1 宽度=8)(实际时间=206.399..206.400 行=1 循环=500) 输出:LEAST('2147483647'::bigint, sum((pb.nr_size)::integer)) 缓冲区:共享命中=373771 读取=153484 -> 嵌套循环(成本=0.87..805.58 行=83 宽度=4)(实际时间=1.468..206.247 行=219 循环=500) 输出:pb.nr_size 内在唯一性:真 缓冲区:共享命中=373771 读取=153484 -> 在 db.tb_pr_dc pd 上使用 tb_pr_dc_in05 进行索引扫描(成本=0.43..104.02 行=83 宽度=4)(实际时间=0.233..49.289 行=219 循环=500) 输出:pd.id_pr_dc、pd.ds_pr_dc、pd.id_pr、pd.id_user_in、pd.id_user_ex、pd.dt_in、pd.dt_ex、pd.ds_mt_ex、pd.in_at、pd.id_tp_pr_dc、pd.id_pr_xz (...) 索引条件:((pd.id_pr)::integer = pt.id_pr) 缓冲区:共享命中=24859 读取=64222 -> 使用 tb_pr_dc_bn_pk 对 db.tb_pr_dc_bn pb 进行索引扫描(成本=0.43..8.45 行=1 宽度=8)(实际时间=0.715..0.715 行=1 循环=109468) 输出:pb.id_pr_dc_bn, pb.ds_ex, pb.ds_md_dc, pb.ds_m5_dc, pb.nm_aq, pb.id_user, pb.dt_in, pb.ob_pr_dc, pb.nr_size, pb.ds_sg, pb.ds_cr_ch, pb.id_user_ ( ...) 指数条件:((pb.id_pr_dc_bn)::integer = (pd.id_pr_dc_bn)::integer) 缓冲区:共享命中=348912 读取=89262 规划时间:1.151 ms 执行时间:103206.243 毫秒

逻辑是:对于每个选择的id_pr(在 500 个 id 的列表中)计算与其关联的整数列 pb.nr_size 的总和,返回该数量与数字 2,147,483,647 之间的较小值。结果必须包含 500 行,每个 id 一行,并且我们已经知道它们将匹配子查询中的至少一行,因此不会产生空值。

索引tb_pr_dc_in05 只是id_pr 上的一个b-tree,它是整数类型。索引tb_pr_dc_bn_pk 只是主键id_pr_dc_bn 上的b-tree,它也是整数类型。表 tb_pr_dc 对于每个 id_pr 都有很多行。实际上,我们在 tb_pr_dc 中有 209,217 个唯一的 id_prs,总共 13,910,855 行。表tb_pr_dc_bn 的行数相同。

可以看出,我们定义了 500 个 id 来查询 tb_pr_dc,找到 109,468 行(不到表大小的 1%),然后在 tb_pr_dc_bn 中找到相同的数量。 Imo,索引看起来很好,要评估的行数很少,所以我不明白为什么运行这个查询要花这么多时间。许多其他查询在其他表上读取更多数据并进行更多计算运行良好。 DBA 刚刚运行了重新索引和真空分析,但它仍然以同样缓慢的方式运行。我们在 Linux 上运行 PostgreSQL 11。我在没有并发访问的副本中运行此查询。

我可能缺少什么可以提高此查询性能?

感谢您的关注。

【问题讨论】:

样本数据、预期结果和清晰的逻辑解释会有所帮助。 您需要向我们展示表和索引定义,以及每个表的行数。也许您的表格定义不佳。也许索引没有正确创建。也许您认为您在该列上没有索引。没有看到表和索引定义,我们无法判断。我们需要行计数,因为这会影响查询计划。 least(2147483647, sum(pb.nr_size)) 应该做什么?如果 SUM 为 NULL,您是否尝试分配一个值? COALESCE 是用于此目的的正确工具。 我正在编辑以解释查询的逻辑。 为什么是子查询?您可以在 where 子句中直接使用列表。为什么会有无用的1=1 条件?我认为您可以将查询简化为this。 tb_pr_dc (id_pr, id_pr_dc_bn)tb_pr_dc_bn (id_pr_dc_bn) 上的索引应该会有所帮助 【参考方案1】:

时间花在跳遍整个表格以找到 109468 个随机分散的行,发出随机 IO 请求来这样做。您可以验证是否打开 track_io_timing 并重做计划(可能只是让它全局打开,默认情况下,开销很低,它产生的价值很高),但我确信我不需要在得出这个结论之前先看看那个输出。其他更快的查询可能访问更少的磁盘页面,因为它们访问的数据更紧凑,或者被组织以便可以更按顺序读取。事实上,考虑到它必须阅读多少页,我会说你的查询相当快。

您问为什么在计划的内部节点中输出了这么多列。这样做的原因是 PostgreSQL 通常只是传递指向元组在 shared_buffers 中所在位置的指针,并且被指向的元组具有表本身具有的列。它可以分配内存来存储重新格式化的元组版本,去掉不必要的列,但这通常会更多,而不是更少。如果无论如何复制和重新形成元组是一个原因,它会在这样做时删除无关的列。但它不会无缘无故地这样做。

加快此速度的一种方法是创建索引以启用仅索引扫描。那就是on tb_pr_dc (id_pr, id_pr_dc_bn)on tb_pr_dc_bn (id_pr_dc_bn, nr_size)

如果这还不够,可能还有其他方法可以改进;但是,如果我一直被表名和列名的一长串难以记住的、不可发音的乱码分散注意力,我就无法思考这些问题。

【讨论】:

关于提问者,我同意你关于难以阅读的列名和表名的评论。关于查询的推理对于强大的软件至关重要。选择有意义的名字!

以上是关于提高 Postgresql 查询性能的主要内容,如果未能解决你的问题,请参考以下文章

使用嵌套循环提高 SQL 查询的性能 - PostgreSQL

提高 Postgresql 查询性能

提高 PostgreSQL 查询性能

提高 Postgres 性能

如何提高 PostgreSQL 9.5 中的查询性能?

如何提高 PostgreSQL LIKE %text% 查询性能