【问题标题】:Slow Postgres 9.3 Queries, again再次缓慢的 Postgres 9.3 查询
【发布时间】:2017-04-26 20:00:06
【问题描述】:

这是Slow Postgres 9.3 queries 问题的后续。

新索引肯定有帮助。但是我们看到有时查询在实践中比我们运行 EXPLAIN ANALYZE 时要慢得多。下面是一个示例,在生产数据库上运行:

explain analyze SELECT * FROM messages WHERE groupid=957 ORDER BY id DESC LIMIT 20 OFFSET 31980;
                                                                       QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=127361.90..127441.55 rows=20 width=747) (actual time=152.036..152.143 rows=20 loops=1)
   ->  Index Scan Backward using idx_groupid_id on messages  (cost=0.43..158780.12 rows=39869 width=747) (actual time=0.080..150.484 rows=32000 loops=1)
         Index Cond: (groupid = 957)
 Total runtime: 152.186 ms
(4 rows)

启用慢速查询日志记录后,我们看到此查询的实例耗时超过 2 秒。我们也有log_lock_waits=true,大约在同一时间没有报告慢锁。什么可以解释执行时间的巨大差异?

【问题讨论】:

  • 当您explain analyze 时,您可能已经在缓存中获得了“热”数据。在这些情况下,您可能不这样做,因此完成了更多 I/O。使用explain (buffers, analyze) 查看缓冲区使用情况。
  • 你需要偏移量做什么?如果要分页,请阅读this。也许您可以通过不同的方式来加快处理速度。

标签: postgresql indexing sql-execution-plan explain postgresql-performance


【解决方案1】:

LIMIT x OFFSET y 通常不会比LIMIT x + y 快多少。较大的OFFSET总是相对昂贵。建议的索引 in the linked question 有帮助,但是虽然您无法从中获得 index-only scans,但 Postgres 仍然必须检查堆中的可见性(主要关系)至少 x + y 行确定正确的结果。

SELECT *
FROM   messages
WHERE  groupid = 957
ORDER  BY id DESC
LIMIT  20
OFFSET 31980;

索引(groupid,id) 上的CLUSTER 将有助于增加堆中数据的局部性并减少每个查询要读取的数据页数。绝对是一场胜利。但是,如果所有groupid 都同样可能被查询,那并不能消除缓存内存太少的瓶颈。如果您有并发访问,请考虑使用 pg_repack 而不是 CLUSTER

您真的需要返回所有列吗? (SELECT *) 如果您只需要返回几个小列,启用仅索引扫描 的覆盖索引可能会有所帮助。 (不过,autovacuum 必须足够强大以应对对表的写入。只读表是理想的。)

另外,根据您的链接问题,您的表在磁盘上是 32 GB。 (通常在 RAM 中多一点)。 (groupid,id) 上的索引增加了另外 308 MB至少(没有任何膨胀):

SELECT pg_size_pretty(7337880.0 * 44);  -- row count * tuple size

您有 8 GB RAM,其中大约 4.5 GB 用于缓存 (effective_cache_size = 4608MB)。这足以缓存索引以供重复使用,但还不足以缓存整个表。

如果您的查询碰巧在缓存中找到数据页,则速度很快。否则,没有那么多。差别很大,即使使用 SSD 存储(使用 HDD 时更是如此)。

与此查询没有直接关系,但 8 MB 的 work_mem (work_mem = 7864kB) 对于您的设置来说似乎太小了。根据各种其他因素,我会将其设置为至少 64MB(除非您有许多带有排序/散列操作的并发查询)。就像@Craig 评论的那样,EXPLAIN (BUFFERS, ANALYZE) 可能会告诉我们更多信息。

最佳查询计划还取决于值频率。如果只有少数行通过过滤器,则某些groupid 的结果可能为空,并且查询相对较快。如果必须获取表的大部分内容,则普通的顺序扫描会胜出。您需要有效的表统计信息(再次autovacuum)。 groupid 的统计目标可能更大:

【讨论】:

  • 哦,Postgres 9.3 已经过时了。升级到 9.6 可能会有所帮助。除了许多其他方面,大表的性能也得到了各种改进。
  • PG 9.6 的小注释 - 我发现 pg 9.6.1 在相同的硬件上需要比我们之前在 pg 9.5 上使用的更高的 work_mem 设置。我发现它是因为一些在 9.5 上运行良好的查询的成本在 9.6 上突然变得绝对可怕
【解决方案2】:

由于OFFSET 很慢,另一种方法是使用另一列和一些索引准备来模拟OFFSET。我们需要在表上有一个 UNIQUE 列(如 PRIMARY KEY)。如果没有,可以添加:

CREATE SEQUENCE messages_pkey_seq ;
ALTER TABLE messages 
  ADD COLUMN message_id integer DEFAULT nextval('messages_pkey_seq');

接下来我们为OFFSET 模拟创建position 列:

ALTER TABLE messages ADD COLUMN position INTEGER;
UPDATE messages SET position = q.position FROM (SELECT message_id,
  row_number() OVER (PARTITION BY group_id ORDER BY id DESC) AS position
  FROM messages ) AS q WHERE q.message_id=messages.message_id ;
CREATE INDEX ON messages ( group_id, position ) ;

现在我们已经为 OP 中的新版本查询做好了准备:

SELECT * FROM messages WHERE group_id = 957 AND
  position BETWEEN 31980 AND (31980+20-1) ;

【讨论】:

  • 不利的一面是,这种方法只适用于只读表,或者position列需要在更改后重新计算才能正常工作
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-06
  • 1970-01-01
  • 2017-07-30
  • 1970-01-01
  • 2016-10-13
相关资源
最近更新 更多