查询期间从磁盘检索到什么?


14

很简单的问题,可能在某个地方回答了,但是我似乎无法为Google形成正确的搜索问题...

在特定表的子集上进行查询时,该表中的列数是否会影响查询的性能?

例如,如果表Foo有20列,但是我的查询只选择了其中的5列,那么拥有20列(相对于说10列)是否会影响查询性能?为简单起见,假设这5列中包含WHERE子句中的任何内容。

除了操作系统的磁盘缓存之外,我还担心Postgres的缓冲区缓存的使用。我对Postgres的物理存储设计非常了解。表存储在多页中(默认每页大小为8k),但我不太了解如何从那里排列元组。PG是否足够聪明,仅从磁盘中获取包含这5列的数据?


您正在谈论要获取50个字节,而不要获取剩余的150个字节。您的磁盘读取的增量可能会更大!
Andomar 2012年

您从哪里得到这些数字?
Jmoney12年

Answers:


15

行的物理存储在“ 数据库页面布局 ”文档中进行了描述。同一行的列内容都存储在同一磁盘页面中,但TOAST的内容(太大而无法容纳在页面中)是一个例外。内容在每一行中按顺序提取,如下所述:

要读取数据,您需要依次检查每个属性。首先根据空位图检查该字段是否为空。如果是,请转到下一个。然后确保对齐正确。如果该字段是固定宽度的字段,则所有字节都将简单放置。

在最简单的情况下(没有TOAST列),即使需要的列很少,postgres也会获取整行。因此,在这种情况下,答案是肯定的,拥有更多的列可能会对浪费的缓冲区高速缓存产生明显的不利影响,特别是如果列的内容很大而仍在TOAST阈值以下时。

现在是TOAST情况:当单个字段超过〜2kB时,引擎会将字段内容存储到单独的物理表中。当整行都无法放入页面时(默认为8kB),它也会起作用:某些字段移至TOAST存储。Doc说:

如果它是一个可变长度的字段(attlen = -1),则要复杂一些。所有可变长度数据类型都共享公用的标头结构struct varlena,其中包括存储值的总长度和一些标志位。根据标志的不同,数据可以是内联的或在TOAST表中。它也可能被压缩

不需要显式获取TOAST的内容时,因此它们对要获取的页面总数的影响很小(每列几个字节)。这解释了@dezso答案中的结果。

至于写操作,每行及其所有列都将在每次UPDATE时完全重写,无论更改了哪些列。因此,拥有更多的列显然会增加写入的成本。


那是一个无耻的回答。正是我要的东西。谢谢。
Jmoney38 2012年

1
我在这里找到了有关行结构(pageinspect和一些示例用法)的很好的资源。
Jmoney38 2012年

10

Daniel的答案集中在读取单个行的成本上。在这种情况下:将固定大小的NOT NULL列放在表中首先会有所帮助。将相关列放在第一位(您查询的列)会有所帮助。通过列中使用对齐俄罗斯方块可以最大程度地减少填充(由于数据对齐)而有所帮助。但最重要的影响尚未提及,特别是对于大桌子。

显然,附加的列会使行覆盖更多的磁盘空间,因此,一个数据页上可以容纳的行更少(默认为8 kB)。各个行分布在更多页面上。数据库引擎通常必须获取整个页面,而不是单个行。只要必须读取相同数量的页面,各个行是较小还是较大就无关紧要。

如果查询获取(相对)大表的一小部分,其中行被或多或少地随机分布在整个表中(由索引支持),这将导致大致相同数量的页面读取,而无需考虑到行大小。在这种(罕见)情况下,无关的列不会使您的速度减慢。

通常,您将获取按顺序或接近度输入的补丁或行簇,并共享数据页。由于混乱,这些行分散开来,必须读取更多的磁盘页面才能满足您的查询要求。通常,必须阅读更多页面是查询速度较慢的最重要原因。这就是为什么无关列使查询变慢的最重要因素。

对于大型数据库,通常没有足够的RAM将其全部保留在高速缓存中。较大的行占用更多的缓存,更多的争用,更少的缓存命中次数,更多的磁盘I / O。而且磁盘读取通常昂贵得多。固态硬盘的情况要少得多,但仍存在很大差异。这增加了关于页面读取的上述要点。

可能或不可能,如果不相关的列敬酒-ED关系。相关的列也可以进行TOAST处理,从而带回许多相同的效果。


1

一个小测试:

CREATE TABLE test2 (
    id serial PRIMARY KEY,
    num integer,
    short_text varchar(32),
    longer_text varchar(1000),
    long_long_text text
);

INSERT INTO test2 (num, short_text, longer_text, long_long_text)
SELECT i, lpad('', 32, 'abcdefeghji'), lpad('', 1000, 'abcdefeghji'), lpad('', (random() * 10000)::integer, 'abcdefeghji')
FROM generate_series(1, 10000) a(i);

ANALYZE test2;

SELECT * FROM test2;
[...]
Time: 1091.331 ms

SELECT num FROM test2;
[...]
Time: 21.310 ms

将查询限制为前250行(WHERE num <= 250)分别导致34.539 ms和8.343 ms。long_long_text从此有限的集合中选择所有内容都将花费18.432 ms。从您的角度来看,这表明PG足够聪明。


好吧,我当然感谢您的投入。但是,我不能肯定地说此测试场景可以证明我最初提出的内容。有几个问题。例如,当您第一次运行“ SELECT * FROM test2”时,它应该已经填充了共享缓冲区高速缓存。从磁盘检索该查询将花费更长的时间。因此,第二个查询理论上会更快,因为它是从SB缓存中获取的。但是我同意PG仅根据您以后的测试/比较来获取需要的行是“建议”。
Jmoney12年

没错,此测试(简单)存在缺陷。如果我有足够的时间,我也会尝试涵盖这些内容。
dezso 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.