为什么 seq/index 扫描需要这么长时间,而 运行 稍后查询?如何让它快?
Why do seq/index scans take so long when running query after a while? How to make it fast?
问题:
我有一个连接三个 table 的查询。每当我 运行 这个查询一段时间后(比如 24 小时),执行起来会花费很多时间。但从那时起,它会执行得非常快(快 70 倍)。想知道第一次执行这么久是什么问题,如何解决
Table 条件:
table 是:property_2
、property_attribute_2
和 property_address_2
。每个都是更大的 table(即 property
、property_attribute
和 property_address
)的分区。此外,property_attribute_2
和 property_address_2
中的行使用列 property_id
具有对 property_2
的引用键。这些列(property.id
、property_attribute_2.property_id
和 property_address_2.property_id
)都已编入索引。
查询是:
select * from public.property_2 a
inner join public.property_attribute_2 b on a.id = b.property_id
left join public.property_address_2 c on a.id=c.property_id
我运行一段时间后的查询计划是:
Hash Right Join (cost=670010.33..983391.75 rows=2477776 width=185) (actual time=804159.499..1065892.338 rows=2477924 loops=1)
Hash Cond: (c.property_id = a.id)
-> Seq Scan on property_address_2 c (cost=0.00..131660.48 rows=4257948 width=72) (actual time=289.781..247906.955 rows=4257973 loops=1)
-> Hash (cost=595483.13..595483.13 rows=2477776 width=117) (actual time=803833.183..803833.185 rows=2477921 loops=1)
Buckets: 32768 Batches: 128 Memory Usage: 3165kB
-> Hash Join (cost=94193.96..595483.13 rows=2477776 width=117) (actual time=98061.326..802753.642 rows=2477921 loops=1)
Hash Cond: (a.id = b.property_id)
-> Seq Scan on property_2 a (cost=0.00..265463.84 rows=6176884 width=105) (actual time=1349.284..696922.438 rows=4272433 loops=1)
-> Hash (cost=48702.76..48702.76 rows=2477776 width=20) (actual time=95497.307..95497.308 rows=2477921 loops=1)
Buckets: 65536 Batches: 64 Memory Usage: 2624kB
-> Seq Scan on property_attribute_2 b (cost=0.00..48702.76 rows=2477776 width=20) (actual time=464.476..94126.890 rows=2477921 loops=1)
Planning time: 4.034 ms
Execution time: 1065995.827 ms
第一个运行之后的查询计划是:
Hash Right Join (cost=670010.33..983391.75 rows=2477776 width=185) (actual time=8828.873..13764.283 rows=2477924 loops=1)
Hash Cond: (c.property_id = a.id)
-> Seq Scan on property_address_2 c (cost=0.00..131660.48 rows=4257948 width=72) (actual time=0.050..1411.877 rows=4257973 loops=1)
-> Hash (cost=595483.13..595483.13 rows=2477776 width=117) (actual time=8826.620..8826.623 rows=2477921 loops=1)
Buckets: 32768 Batches: 128 Memory Usage: 3165kB
-> Hash Join (cost=94193.96..595483.13 rows=2477776 width=117) (actual time=1356.224..7925.850 rows=2477921 loops=1)
Hash Cond: (a.id = b.property_id)
-> Seq Scan on property_2 a (cost=0.00..265463.84 rows=6176884 width=105) (actual time=0.034..2652.013 rows=4272433 loops=1)
-> Hash (cost=48702.76..48702.76 rows=2477776 width=20) (actual time=1354.828..1354.829 rows=2477921 loops=1)
Buckets: 65536 Batches: 64 Memory Usage: 2624kB
-> Seq Scan on property_attribute_2 b (cost=0.00..48702.76 rows=2477776 width=20) (actual time=0.023..630.081 rows=2477921 loops=1)
Planning time: 1.181 ms
Execution time: 13872.977 ms
还值得注意的是,我在这台机器上还有几个其他 Postgres 数据库,不同的作业定期在这些数据库上使用不同的 tables。
不同之处在于缓存:第一次,数据是从磁盘读取的,在随后的运行中,它们是在 RAM 中找到的。 运行 EXPLAIN (ANALYZE, BUFFERS)
用 track_io_timing = on
确认。
但是,您的 I/O 系统似乎真的很慢,或者您的表非常臃肿。 EXPLAIN (ANALYZE, BUFFERS)
会显示读取了多少块,所以你会知道。
如果臃肿确实是你的问题,VACUUM (FULL)
会有所帮助。
如果 冷缓存 是问题所在,似乎是这种情况,您可以在 运行 查询之前预热它。 Postgres 附带附加模块 pg_prewarm
,提供一系列工具来填充缓存。
此处说明如何设置:
那么你 运行 就像:
SELECT pg_prewarm('public.property_2', 'prefetch');
SELECT pg_prewarm('public.property_attribute_2', 'prefetch');
SELECT pg_prewarm('public.property_address_2', 'prefetch');
当然,如果您总是 运行 相同的 SELECT
查询而没有过滤谓词,您不妨只 运行 相同的查询来填充缓存,而不使用花哨的模块。可能安排了 cron 作业?
... are all indexed.
正如您在 EXPLAIN
输出中看到的那样,您的索引未被使用。您在没有过滤谓词的情况下获取所有行,因此索引通常无济于事。你用 SELECT *
来做到这一点,即从所有连接的 table 中获取所有列,因此仅索引扫描也结束了。您可以通过在 SELECT
列表中仅列出您实际需要的列来提高性能。
显然,更多 RAM(以及 PostgreSQL 缓冲区缓存的适当配置)也有帮助。
或者您可以使用 VACUUM
(FULL
) 或使用具有适当列类型和顺序的优化 table 定义来减少 RAM 需求。不仅是手头的 tables,还有其他 tables 竞争相同的资源(从而从缓存中驱逐“你的”块)。参见:
- Calculating and saving space in PostgreSQL
问题:
我有一个连接三个 table 的查询。每当我 运行 这个查询一段时间后(比如 24 小时),执行起来会花费很多时间。但从那时起,它会执行得非常快(快 70 倍)。想知道第一次执行这么久是什么问题,如何解决
Table 条件:
table 是:property_2
、property_attribute_2
和 property_address_2
。每个都是更大的 table(即 property
、property_attribute
和 property_address
)的分区。此外,property_attribute_2
和 property_address_2
中的行使用列 property_id
具有对 property_2
的引用键。这些列(property.id
、property_attribute_2.property_id
和 property_address_2.property_id
)都已编入索引。
查询是:
select * from public.property_2 a
inner join public.property_attribute_2 b on a.id = b.property_id
left join public.property_address_2 c on a.id=c.property_id
我运行一段时间后的查询计划是:
Hash Right Join (cost=670010.33..983391.75 rows=2477776 width=185) (actual time=804159.499..1065892.338 rows=2477924 loops=1)
Hash Cond: (c.property_id = a.id)
-> Seq Scan on property_address_2 c (cost=0.00..131660.48 rows=4257948 width=72) (actual time=289.781..247906.955 rows=4257973 loops=1)
-> Hash (cost=595483.13..595483.13 rows=2477776 width=117) (actual time=803833.183..803833.185 rows=2477921 loops=1)
Buckets: 32768 Batches: 128 Memory Usage: 3165kB
-> Hash Join (cost=94193.96..595483.13 rows=2477776 width=117) (actual time=98061.326..802753.642 rows=2477921 loops=1)
Hash Cond: (a.id = b.property_id)
-> Seq Scan on property_2 a (cost=0.00..265463.84 rows=6176884 width=105) (actual time=1349.284..696922.438 rows=4272433 loops=1)
-> Hash (cost=48702.76..48702.76 rows=2477776 width=20) (actual time=95497.307..95497.308 rows=2477921 loops=1)
Buckets: 65536 Batches: 64 Memory Usage: 2624kB
-> Seq Scan on property_attribute_2 b (cost=0.00..48702.76 rows=2477776 width=20) (actual time=464.476..94126.890 rows=2477921 loops=1)
Planning time: 4.034 ms
Execution time: 1065995.827 ms
第一个运行之后的查询计划是:
Hash Right Join (cost=670010.33..983391.75 rows=2477776 width=185) (actual time=8828.873..13764.283 rows=2477924 loops=1)
Hash Cond: (c.property_id = a.id)
-> Seq Scan on property_address_2 c (cost=0.00..131660.48 rows=4257948 width=72) (actual time=0.050..1411.877 rows=4257973 loops=1)
-> Hash (cost=595483.13..595483.13 rows=2477776 width=117) (actual time=8826.620..8826.623 rows=2477921 loops=1)
Buckets: 32768 Batches: 128 Memory Usage: 3165kB
-> Hash Join (cost=94193.96..595483.13 rows=2477776 width=117) (actual time=1356.224..7925.850 rows=2477921 loops=1)
Hash Cond: (a.id = b.property_id)
-> Seq Scan on property_2 a (cost=0.00..265463.84 rows=6176884 width=105) (actual time=0.034..2652.013 rows=4272433 loops=1)
-> Hash (cost=48702.76..48702.76 rows=2477776 width=20) (actual time=1354.828..1354.829 rows=2477921 loops=1)
Buckets: 65536 Batches: 64 Memory Usage: 2624kB
-> Seq Scan on property_attribute_2 b (cost=0.00..48702.76 rows=2477776 width=20) (actual time=0.023..630.081 rows=2477921 loops=1)
Planning time: 1.181 ms
Execution time: 13872.977 ms
还值得注意的是,我在这台机器上还有几个其他 Postgres 数据库,不同的作业定期在这些数据库上使用不同的 tables。
不同之处在于缓存:第一次,数据是从磁盘读取的,在随后的运行中,它们是在 RAM 中找到的。 运行 EXPLAIN (ANALYZE, BUFFERS)
用 track_io_timing = on
确认。
但是,您的 I/O 系统似乎真的很慢,或者您的表非常臃肿。 EXPLAIN (ANALYZE, BUFFERS)
会显示读取了多少块,所以你会知道。
如果臃肿确实是你的问题,VACUUM (FULL)
会有所帮助。
如果 冷缓存 是问题所在,似乎是这种情况,您可以在 运行 查询之前预热它。 Postgres 附带附加模块 pg_prewarm
,提供一系列工具来填充缓存。
此处说明如何设置:
那么你 运行 就像:
SELECT pg_prewarm('public.property_2', 'prefetch');
SELECT pg_prewarm('public.property_attribute_2', 'prefetch');
SELECT pg_prewarm('public.property_address_2', 'prefetch');
当然,如果您总是 运行 相同的 SELECT
查询而没有过滤谓词,您不妨只 运行 相同的查询来填充缓存,而不使用花哨的模块。可能安排了 cron 作业?
... are all indexed.
正如您在 EXPLAIN
输出中看到的那样,您的索引未被使用。您在没有过滤谓词的情况下获取所有行,因此索引通常无济于事。你用 SELECT *
来做到这一点,即从所有连接的 table 中获取所有列,因此仅索引扫描也结束了。您可以通过在 SELECT
列表中仅列出您实际需要的列来提高性能。
显然,更多 RAM(以及 PostgreSQL 缓冲区缓存的适当配置)也有帮助。
或者您可以使用 VACUUM
(FULL
) 或使用具有适当列类型和顺序的优化 table 定义来减少 RAM 需求。不仅是手头的 tables,还有其他 tables 竞争相同的资源(从而从缓存中驱逐“你的”块)。参见:
- Calculating and saving space in PostgreSQL