大表中的慢索引扫描

Exe*_*ian 21 postgresql performance index optimization postgresql-performance

2020-08-04 更新:

由于显然仍在定期查看此答案,因此我想提供有关情况的最新信息。我们目前正在使用带有表分区的 PG 11,timestamp并且可以轻松处理表中的数十亿行。仅索引扫描可以挽救生命,没有它就不可能。


使用 PostgreSQL 9.2,我在相对较大的表(200 多万行)上进行慢速查询时遇到问题。我没有尝试任何疯狂的事情,只是增加了历史价值。下面是查询和查询计划输出。

我的表布局:

                                   Table "public.energy_energyentry"
  Column   |           Type           |                            Modifiers
-----------+--------------------------+-----------------------------------------------------------------
 id        | integer                  | not null default nextval('energy_energyentry_id_seq'::regclass)
 prop_id   | integer                  | not null
 timestamp | timestamp with time zone | not null
 value     | double precision         | not null
Indexes:
    "energy_energyentry_pkey" PRIMARY KEY, btree (id)
    "energy_energyentry_prop_id" btree (prop_id)
    "energy_energyentry_prop_id_timestamp_idx" btree (prop_id, "timestamp")
Foreign-key constraints:
    "energy_energyentry_prop_id_fkey" FOREIGN KEY (prop_id) REFERENCES gateway_peripheralproperty(id) DEFERRABLE INITIALLY DEFERRED
Run Code Online (Sandbox Code Playgroud)

数据范围从2012-01-01至今,新数据不断增加。prop_id外键中大约有 2.2k 个不同的值,均匀分布。

我注意到行估计值相差不远,但成本估计值似乎大了 4 倍。这可能不是问题,但我能做些什么吗?

我预计磁盘访问可能是问题所在,因为该表并非一直在内存中。

EXPLAIN ANALYZE 
SELECT SUM("value") 
FROM "energy_energyentry" 
WHERE 
  "prop_id"=82411 
  AND "timestamp">'2014-06-11' 
  AND "timestamp"<'2014-11-11'
;
Run Code Online (Sandbox Code Playgroud)
 Aggregate  (cost=214481.45..214481.46 rows=1 width=8) (actual time=51504.814..51504.814 rows=1 loops=1)
   ->  Index Scan using energy_energyentry_prop_id_timestamp_idx on  energy_energyentry (cost=0.00..214434.08 rows=18947 width=8) (actual time=136.030..51488.321 rows=13578 loops=1)
         Index Cond: ((prop_id = 82411) AND ("timestamp" > '2014-06-11 00:00:00+00'::timestamp with time zone) AND ("timestamp" < '2014-11-11 00:00:00+00'::timestamp with time zone))
 Total runtime: 51504.841 ms
Run Code Online (Sandbox Code Playgroud)

任何建议如何使这更快?
我也很好,只是听说我没有做任何奇怪的事情。

Erw*_*ter 15

您的表很大,跨越整个表的任何索引也是如此。假如说:

  • 仅输入新数据(带timestamp = now()
  • 既不更改也不删除现有行。
  • 您拥有自 2012-01-01 以来的数据,但查询主要针对当年 (?)

我建议使用部分多列(覆盖!)索引

CREATE INDEX ON energy_energyentry (prop_id, "timestamp", value)
WHERE "timestamp" >= '2014-01-01 0:0';  -- adapt to your needs
Run Code Online (Sandbox Code Playgroud)

只包含定期查询的时间范围。随着新条目的出现,有效性会随着时间的推移而恶化。不时重新创建索引。(您可能需要调整您的查询。)请参阅下面的链接答案。

最后一列值仅用于从中获取仅索引扫描。积极的 autovacuum 设置可能有助于保持可见性地图最新,就像@jjanes 已经提到的那样

部分索引应该更容易适应 RAM 并在那里停留更长时间。

您可能需要WHERE在查询中包含此条件,以使规划器了解索引适用于查询,例如:

SELECT sum(value) AS sum_value
FROM   energy_energyentry
WHERE  prop_id = 82411 
AND   "timestamp" > '2014-06-11 0:0' 
AND   "timestamp" < '2014-11-11 0:0'
AND   "timestamp" >= '2014-01-01 0:0'; -- seems redundant, but may be needed
Run Code Online (Sandbox Code Playgroud)

由于您的查询汇总了很多行 ( rows=13578),因此即使使用仅索引扫描,这也需要一些时间。不过,它不应该接近 50 秒。在任何中等体面的硬件上不到一秒钟。

相关(但忽略CLUSTERand FILLFACTOR,如果您可以从中获得仅索引扫描,则两者都无关紧要)

旁白:
由于您目前在 上有一个索引(prop_id, "timestamp"),因此在 上的额外索引的(prop_id)成本可能会超过其价值:


jja*_*nes 5

如果在 (prop_id, "timestamp","value") 上创建索引,则它可以使用仅索引扫描来计算值,而无需访问表。这可以节省大量随机磁盘访问。

为了获得最大的好处,您需要积极地对桌子进行吸尘。对于您希望有效支持仅索引扫描的仅插入表,默认的 autovac 设置不够激进。


归档时间:

查看次数:

21662 次

最近记录:

5 年,3 月 前