慢速Postgres 9.3再次查询

时间:2016-12-12 00:18:21

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

这是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,并且在同一时间内没有报告慢锁。什么可以解释执行时间的巨大差异?

2 个答案:

答案 0 :(得分:3)

LIMIT x OFFSET y的执行速度通常不会比LIMIT x + y快。大OFFSET 总是相对昂贵。建议的索引in the linked question会有所帮助,但是当您无法从中获取仅索引扫描时,Postgres仍然必须检查堆中的可见性(主要关系)至少{{1 }行来确定正确的结果。

x + y
索引SELECT * FROM messages WHERE groupid = 957 ORDER BY id DESC LIMIT 20 OFFSET 31980; 上的

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

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

此外,根据您的链接问题,您的表在磁盘上为32 GB。 (通常在RAM中多一点)。 autovacuum上的索引添加了另外308 MB 至少(没有任何臃肿):

(groupid,id)

您有8 GB RAM,其中大约4.5 GB可用于缓存(SELECT pg_size_pretty(7337880.0 * 44); -- row count * tuple size )。这足以缓存索引以供重复使用,但还不足以缓存整个表。

如果您的查询恰好在缓存中查找数据页面,则速度很快。否则,不是那么多。差异很大,即使是SSD存储(硬盘更多)。

与此查询没有直接关系,但8 MB的effective_cache_size = 4608MB内存(work_)似乎对您的设置来说很小。根据各种其他因素,我将其设置为至少64MB(除非您有许多并发查询与排序/哈希操作)。与@Craig评论一样,work_mem = 7864kB可能会告诉我们更多信息。

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

答案 1 :(得分:1)

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

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

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

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) ;