提高查询速度:大 postgres 表中的简单 SELECT

Posted

技术标签:

【中文标题】提高查询速度:大 postgres 表中的简单 SELECT【英文标题】:Improving query speed: simple SELECT in big postgres table 【发布时间】:2012-10-25 10:45:49 【问题描述】:

我在 Postgres 数据库上的 SELECT 查询中遇到了速度问题。

我有一个表,其中有两个整数列作为键:(int1,int2) 该表有大约 7000 万行。

我需要在这个环境中进行两种简单的 SELECT 查询:

SELECT * FROM table WHERE int1=X;
SELECT * FROM table WHERE int2=X;

这两个选择返回这 7000 万行中的大约 10.000 行。为了尽可能快地工作,我考虑使用两个 HASH 索引,每列一个。不幸的是,结果不是那么好:

                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on lec_sim  (cost=232.21..25054.38 rows=6565 width=36) (actual time=14.759..23339.545 rows=7871 loops=1)
   Recheck Cond: (lec2_id = 11782)
   ->  Bitmap Index Scan on lec_sim_lec2_hash_ind  (cost=0.00..230.56 rows=6565 width=0) (actual time=13.495..13.495 rows=7871 loops=1)
         Index Cond: (lec2_id = 11782)
 Total runtime: 23342.534 ms
(5 rows)

这是其中一个查询的 EXPLAIN ANALYZE 示例。大约需要 23 秒。我的期望是在不到一秒的时间内获得这些信息。

这些是 postgres db 配置的一些参数:

work_mem = 128MB
shared_buffers = 2GB
maintenance_work_mem = 512MB
fsync = off
synchronous_commit = off
effective_cache_size = 4GB

非常感谢任何帮助、评论或想法。

提前谢谢你。

【问题讨论】:

作为您总时间的一部分,其中有多少是将数据发回给您?您是在与数据库相同的机器上运行查询还是通过网络进行查询? @JustBob:解释输出中报告的时间是在服务器上准备查询的时间(没有客户端往返) 哈希索引在 PostgreSQL 中效率不高。您是否尝试过常规的 B-Tree 索引?每一列都有一个索引,还是两者都有一个组合索引?这两个语句中的哪一个是发布的执行计划? 这里的索引查找非常快——所有时间都花在检索实际行上。 23 秒 / 7871 行 = 每行 2.9 毫秒,这对于检索分散在磁盘子系统中的数据是合理的。搜索很慢;您可以 a) 将数据集放入 RAM,b) 购买 SSD,或 c) 提前组织数据以尽量减少搜索。 两个表是一个选项,特别是如果你CLUSTER 两个表。但是,PostgreSQL 9.2 添加了一个名为index only scans 的功能,这在这里特别有用——在所有感兴趣的列上创建一个btree 索引(PostgreSQL 自动保持有序),并且可以(可能)使用来回答查询只有没有额外搜索的索引。 【参考方案1】:

将我的 cmets 提取为答案:此处的索引查找非常快——所有时间都花在检索实际行上。 23 秒 / 7871 行 = 每行 2.9 毫秒,这对于检索分散在磁盘子系统中的数据是合理的。搜索很慢;您可以 a) 将数据集放入 RAM,b) 购买 SSD,或 c) 提前组织数据以尽量减少搜索。

PostgreSQL 9.2 有一个名为index-only scans 的功能,允许它(通常)在不访问表的情况下回答查询。您可以将此与自动维护顺序的btree 索引属性结合使用,以使此查询更快。你提到了int1int2,还有两个浮点数:

CREATE INDEX sometable_int1_floats_key ON sometable (int1, float1, float2);
CREATE INDEX sometable_int2_floats_key ON sometable (int2, float1, float2);

SELECT float1,float2 FROM sometable WHERE int1=<value>; -- uses int1 index
SELECT float1,float2 FROM sometable WHERE int2=<value>; -- uses int2 index

还请注意,这不会神奇地擦除磁盘搜索,它只是将它们从查询时间移动到插入时间。由于您正在复制数据,因此它还会占用您的存储空间。不过,这可能是您想要的取舍。

【讨论】:

非常感谢@willglynn。我想这就是我一直在寻找的。明天我会给它一个机会,我会发布它的表现。 我完全被它的惊人效果以及它对性能的巨大影响所震撼。在包含大约 2000 万行的生产数据库上,创建一个简单的索引将数据库查询从大约 8 秒缩短到大约 20 毫秒。【参考方案2】:

谢谢你。正如您所注意到的,问题在于通过 HD 进行搜索而不是查找索引。您提出了许多解决方案,例如将数据集加载到 RAM 中或购买 SSD HD。但是忘记这两个,涉及到管理数据库本身之外的东西,你提出了两个想法:

    重新组织数据以减少对数据的查找。 使用 PostgreSQL 9.2 功能“仅索引扫描”

由于我在 PostgreSQL 9.1 服务器下,我决定采用选项“1”。

我复制了表格。所以现在我有两次相同的数据相同的表。我为每一个都创建了一个索引,第一个由 (int1) 索引,第二个由 (int2) 索引。然后我通过各自的索引将它们都聚集在一起(CLUSTER table USING ind_intX)。

我现在发布对同一查询的 EXPLAIN ANALYZE,在这些聚簇表之一中完成:

查询计划 -------------------------------------------------- -------------------------------------------------- ----------------------------------------- 在 lec_sim_lec2id 上使用 lec_sim_lec2id_ind 进行索引扫描(成本=0.00..21626.82 行=6604 宽度=36)(实际时间=0.051..1.500 行=8119 循环=1) 索引条件:(lec2_id = 12300)总运行时间: 1.822 毫秒(3 行)

现在搜索速度非常快。我从 23 秒下降到 ~2 毫秒,这是一个令人印象深刻的改进。我认为这个问题已经为我解决了,我希望这对遇到同样问题的其他人也有用。

非常感谢 Willglynn。

【讨论】:

如果你有一个静态数据集,你就完成了。如果没有,您将需要使用触发器来维护已排序的表(以便有一个事实来源),并且您需要定期重新CLUSTER 以在数据更改时维护磁盘顺序。【参考方案3】:

我有一个超慢查询的案例,其中在 3300 万行的表和 24 亿行大小的子表之间执行简单的一对多连接(在 PG v9.1 中)。我对子表的外键索引执行了 CLUSTER,但发现这并没有解决我的查询超时问题,即使是最简单的查询也是如此。运行 ANALYZE 也没有解决问题。

产生巨大差异的是在父表和子表上执行手动 VACUUM。即使父表正在完成其 VACUUM 过程,我也从 10 分钟超时到结果在一秒钟内返回。

我从中得出的结论是,常规的 VACUUM 操作仍然很关键,即使对于 v9.1 也是如此。我这样做的原因是我注意到 autovacuum 至少两周没有在任何一个表上运行,并且从那时起发生了很多 upserts 和 inserts。可能我需要改进 autovacuum 触发器来解决这个问题,但我可以说的是,如果所有内容都被清理干净,一个有几十亿行的 640GB 表确实可以很好地执行。我还没有对表进行分区以获得良好的性能。

【讨论】:

您应该调查为什么 autovacuum 没有运行。您可能有一些处于“空闲事务”模式的会话。确保在代码中正确终止事务。你可能应该让你的 autovacuum 设置更加激进。【参考方案4】:

对于一个非常简单有效的单行,如果您的 postgres 机器上有快速固态存储,请尝试设置:

random_page_cost=1.0

在您的postgresql.conf 中。

默认值为random_page_cost=4.0,它针对具有高寻道时间的存储进行了优化,例如旧的旋转磁盘。这改变了寻找的成本计算,并且减少了对内存的依赖(最终可能会被交换)

仅此设置就可以将我在包含几百万条记录的长表上的过滤查询从 8 秒缩短到 2 秒。

另一个主要改进来自于使用我表上的所有布尔列创建索引。这将 2 秒的查询减少到大约 1 秒。检查@willglynn 的答案。

希望这会有所帮助!

【讨论】:

以上是关于提高查询速度:大 postgres 表中的简单 SELECT的主要内容,如果未能解决你的问题,请参考以下文章

结合关系查询提高 Postgres jsonb 查询的性能

加入大表时,postgres 查询速度慢

数据库----提高大数据量查询速度

Postgres查询以查找连接表中是不是已存在组合

提高 Postgres 性能

补12.关于mysql的索引回顾