选择多行时Postgres慢

Posted

技术标签:

【中文标题】选择多行时Postgres慢【英文标题】:Postgres slow when selecting many rows 【发布时间】:2020-01-29 18:44:25 【问题描述】:

我正在运行 Postgres 11。 我有一个包含 1.000.000(100 万)行的表,每行的大小为 40 个字节(它包含 5 列)。这等于 40MB。

当我执行时(通过 DBeaver、DataGrid 等直接在 DB 上执行-不通过 Node、Python 等调用):

SELECT * FROM TABLE

第一次需要40秒(即使是第一次也不是很慢)。

我的表的 CREATE 语句:

CREATE TABLE public.my_table_1 (
    c1 int8 NOT NULL GENERATED ALWAYS AS IDENTITY,
    c2 int8 NOT NULL,
    c3 timestamptz NULL,
    c4 float8 NOT NULL,
    c5 float8 NOT NULL,
    CONSTRAINT my_table_1_pkey PRIMARY KEY (id)
);
CREATE INDEX my_table_1_c3_idx ON public.my_table_1 USING btree (c3);
CREATE UNIQUE INDEX my_table_1_c2_idx ON public.my_table_1 USING btree (c2);

在 5 个随机表上:EXPLAIN (ANALYZE, BUFFERS) select * from [table_1...2,3,4,5]

Seq Scan on table_1  (cost=0.00..666.06 rows=34406 width=41) (actual time=0.125..7.698 rows=34406 loops=1)
  Buffers: shared read=322
Planning Time: 15.521 ms
Execution Time: 10.139 ms

Seq Scan on table_2  (cost=0.00..9734.87 rows=503187 width=41) (actual time=0.103..57.698 rows=503187 loops=1)
  Buffers: shared read=4703
Planning Time: 14.265 ms
Execution Time: 74.240 ms

Seq Scan on table_3  (cost=0.00..3486217.40 rows=180205440 width=41) (actual time=0.022..14988.078 rows=180205379 loops=1)
  Buffers: shared hit=7899 read=1676264
Planning Time: 0.413 ms
Execution Time: 20781.303 ms

Seq Scan on table_4  (cost=0.00..140219.73 rows=7248073 width=41) (actual time=13.638..978.125 rows=7247991 loops=1)
  Buffers: shared hit=7394 read=60345
Planning Time: 0.246 ms
Execution Time: 1264.766 ms

Seq Scan on table_5  (cost=0.00..348132.60 rows=17995260 width=41) (actual time=13.648..2138.741 rows=17995174 loops=1)
  Buffers: shared hit=82 read=168098
Planning Time: 0.339 ms
Execution Time: 2730.355 ms

当我将 LIMIT 1.000.000 添加到 table_5 时(它包含 170 万行)

Limit  (cost=0.00..19345.79 rows=1000000 width=41) (actual time=0.007..131.939 rows=1000000 loops=1)
  Buffers: shared hit=9346
  ->  Seq Scan on table_5(cost=0.00..348132.60 rows=17995260 width=41) (actual time=0.006..68.635 rows=1000000 loops=1)
        Buffers: shared hit=9346
Planning Time: 0.048 ms
Execution Time: 164.133 ms

当我在 2 个日期之间添加 WHERE 子句时(我使用 DataDog 软件监控下面的查询,结果在这里(获取时最大~ 31K 行/秒):https://www.screencast.com/t/yV0k4ShrUwSd):

Seq Scan on table_5 (cost=0.00..438108.90 rows=17862027 width=41) (actual time=0.026..2070.047 rows=17866766 loops=1)
  Filter: (('2018-01-01 00:00:00+04'::timestamp with time zone < matchdate) AND (matchdate < '2020-01-01 00:00:00+04'::timestamp with time zone))
  Rows Removed by Filter: 128408
  Buffers: shared hit=168180
Planning Time: 14.820 ms
Execution Time: 2673.171 ms

所有表的 c3 列都有唯一索引。

数据库的大小总共约为 500GB。 服务器有 16 个核心和 112GB M2 内存。

我尝试优化 Postgres 系统变量 - 例如:WorkMem(1GB)、shared_buffer(50GB)、effective_cache_size (20GB) - 但它似乎没有改变任何东西(我知道设置已应用 - 因为我可以看到服务器分配的空闲内存量有很大差异)。

我知道数据库太大,所有数据都无法存储在内存中。但是我可以做些什么来提高查询的性能/速度?

【问题讨论】:

提示:如果你使用CreatedDate BETWEEN ... AND ... 而不是这样表达它会更有意义。 A) 听起来您确实需要该列上的索引。 B) 使用ISO 8601 date format,如YYYY-MM-DD。这些可以被索引。您在此处的日期无法排序,对 Postgres 毫无意义。你在这里的方式是 11 月在 2 月之前,但在 1 月之后,或者如果这是 DD-MM-YYYY 远不清楚,那么 1 月 11 日可能会在 1 月 2 日之前。 M2内存?你的意思是m.2? 你能分享CREATE TABLE的声明吗? @tadman PostgreSQL 可以毫无问题地理解“01-01-2019”作为日期的含义,并将根据该理解使用索引。它参考 DateStyle 来决定 MDY 和 DMY。 【参考方案1】:

确保 CreatedDate 已编入索引。

确保CreatedDate 使用的是date column type。这将在存储(仅 4 个字节)、性能方面更加高效,并且您可以使用所有内置的 date formatting 和 functions。

避免select *,只选择您需要的列。

使用YYYY-MM-DD ISO 8601 format。这与性能无关,但会避免很多歧义。


真正的问题可能是您有数千个表,您经常使用这些表将数百个表合并。这表明需要重新设计架构以简化查询并获得更好的性能。

联合和日期更改检查表明存在大量冗余。也许您已经按日期对表进行了分区。 Postgres 有自己的内置 table partitioning,这可能会有所帮助。

没有更多细节,我只能说。也许再问一个关于你的架构的问题。

【讨论】:

1) CreatedDate 已编入索引 - 请查看更新后的帖子。 2) CreatedDate 的列类型是 timestamptz - 这不是可以/最佳吗? 3)我需要小表中的所有列。 @PabloDK timestamptz 很好。只是不要将日期和时间存储为字符串。为了让我提供更多帮助,您必须描述为什么要合并 100 个表。 1) 因为我只需要所有给定表格中的大量混合子周期。 2)但是请告诉我为什么 1.000.000 行需要 40 秒才能获取(当每行只有 41 个字节时)? @PabloDK 如果您指的是select * from table 第一次需要 40 秒,但之后非常快,那是因为您的缓存很冷,或者可能是 GUI 工具处理和渲染 100 万行。在psql 中尝试。否则,我们需要查看这些表的样本是否被联合。 是的 - 我是裁判。到“从表中选择 *”需要 40 秒(执行总是需要很长时间):-) 好的 - 我也会尝试使用 psql 运行测试......否则没有什么可做的?【参考方案2】:

没有看到EXPLAIN (ANALYZE, BUFFERS),我们只能猜测。

但我们可以做一些很好的推测。

在 CreatedDate 上对索引上的表进行聚类。这将允许更顺序地访问数据,允许更多的预读(但这对于某些类型的存储可能没有多大帮助)。如果表的写入负载很高,它们可能不会保持集群状态,因此您有时会重新集群它们。如果它们是静态的,这可能是一次性事件。

获取更多内存。如果你想像所有数据都在内存中一样执行,那么将所有数据都放入内存中。

获得更快的存储空间,例如一流的 SSD。它不如 RAM 快,但比 HDD 快得多。

【讨论】:

1) 你到底在问什么:解释(分析,缓冲区)?我应该执行什么 SQL 你给你你正在寻找的信息? 2) 内存是三星 m.2 PRO 1TB X 3 / 我所知道的最快/最好的... 3) 服务器中的主板“仅”允许 128GB 内存 我已经对所有表运行了 Vacuum + 分析 - 但它没有改变任何东西。 @PabloDK PostgreSQL EXPLAIN Explained。 Explain 是您找出查询缓慢原因的基本工具。您可以尝试cluster &lt;table&gt; using CreatedDate,这将在您获取CreatedDate 范围时重新排序表格以提高效率。 对 table1 的 Seq Scan (cost=0.00..20728.58 rows=1071458 width=41) Seq Scan on table2 (cost=0.00..328809.16 rows=16996416 width=41) Seq Scan on table3 (cost =0.00..78231.31 行=4043831 宽度=41) @PabloDK 这些是全表扫描。它没有使用索引。但如果没有相关的查询和架构,我们将无能为力。

以上是关于选择多行时Postgres慢的主要内容,如果未能解决你的问题,请参考以下文章

在 Greenplum(Postgres 8.4)中进行多行更新时跟踪错误记录?

索引扫描时 Postgres 不使用索引是更好的选择

Postgres:将元素数组转换为多行

clojure/java.jdbc 和 postgres:Prepared statments 比字符串连接查询慢 100 倍?

Postgres:将单行转换为多行(unpivot)

将多行加入单行中的多列 Netezza/Postgres