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