Postgresql 为什么在索引扫描过程中要读取这么多页面(Postgres 11.2)?

Postgresql 为什么在索引扫描过程中要读取这么多页面(Postgres 11.2)?,postgresql,Postgresql,我们有一个Postgres 11.2数据库,它根据复合键存储时间序列值。给定1个或多个键,查询将尝试查找给定时间约束的每个时间序列中的最新值 当数据未缓存时,我们会遇到查询超时,因为它似乎必须遍历大量页面才能找到数据 以下是解释中的相关部分。我们将获得单个时间序列的数据(本例中有367个值): 其中列分别为2xint4和atimestamptz 假设我正确读取了输出,为什么引擎要遍历374页(考虑到8kb的页面大小,大约3Mb),以返回大约26kb的数据(367行宽74字节) 当我们放大键的数量

我们有一个Postgres 11.2数据库,它根据复合键存储时间序列值。给定1个或多个键,查询将尝试查找给定时间约束的每个时间序列中的最新值

当数据未缓存时,我们会遇到查询超时,因为它似乎必须遍历大量页面才能找到数据

以下是
解释中的相关部分。我们将获得单个时间序列的数据(本例中有367个值):

其中列分别为2x
int4
和a
timestamptz

假设我正确读取了输出,为什么引擎要遍历374页(考虑到8kb的页面大小,大约3Mb),以返回大约26kb的数据(367行宽74字节)

当我们放大键的数量(比如说500个)时,引擎最终会遍历150k页(超过1GB),如果不缓存,这将花费大量时间

注意,基础表中的平均行大小为82字节(超过11列),包含大约700mi行


提前感谢您的任何见解

在索引扫描中找到的367行可能存储在300多个表块中(这在大型表中并不奇怪)。因此,PostgreSQL必须访问所有这些块才能得出结果

如果所有行都集中在几个表块中,这将执行得更好。换句话说,如果索引的逻辑顺序与表中行的物理顺序相对应。用PostgreSQL的术语来说,一个高的值是有益的

您可以使用以下命令强制PostgreSQL以正确的顺序重写整个表

CLUSTER quotes USING quotes_idx;
那么您的查询应该会更快

但也有一些缺点:

  • CLUSTER
    正在运行时,无法访问该表。这通常意味着停机时间

  • 集群之后
    ,性能会很好,但PostgreSQL不维持排序。随后的数据修改将降低相关性

    为了保持查询的良好性能,您必须定期安排
    集群


读取374个块以获得367行并非意外。如前所述,对数据进行聚类是解决这一问题的一种方法。另一种可能是向索引列列表中添加更多的列(通过创建新索引并删除旧索引),这样查询就可以通过只扫描索引来满足


如果同时创建索引,则不需要停机时间。你必须保持桌子真空良好,这可能很难做到,因为autovacuum参数的设计并没有考虑到IOS。它不需要维护,只需要清空,因此如果需要添加到索引中的列的列表(和大小)很小,我更喜欢这种方法。

索引扫描包括查找索引中的行,然后从表中读取该行。缓冲区的数量包括两者。预期行(1)和实际行(367)之间的不匹配有点太高。如果运行
analyze quotes
并重试,计划是否会更改?这个表总共有多少行?也许在这种情况下,序列扫描会更好。谢谢,是的,我想知道。。。我估计表中的每个页面大约有100行(8kb页面/82字节)。我很感激374个命中页面中有一部分是索引页面,但出于论证的考虑,即使一半是索引页面,另一半是表页面,那仍然是187页x100行=18.7k行,仅返回367行……尝试了
分析
,但不幸的是没有改变估计值(假设它正在处理timestamptz列上的范围查询).Table非常有700mi行数!当我看到文档中的
ACCESS EXCLUSIVE
注释时,我正打算尝试一下……我将等到今晚,然后在我们的测试环境中尝试一下。毫无疑问,你知道我在哪里可以找到有关Postgres如何选择在块中放置新行的信息吗(考虑到没有聚集索引等)?行被放置在具有足够可用空间的第一个最佳块中。此信息在可见性映射中进行管理。谢谢-这很有效!对表进行聚类后,查询只命中它以前必须执行的页数的3%。我们将尝试使用
pg_-repack
进行在线聚类,以避免排他表锁定:
CREATE UNIQUE INDEX quotes_idx ON quotes.quotes USING btree (client_id, quote_detail_id, effective_at);
CLUSTER quotes USING quotes_idx;