PostgreSQL慢于预期

时间:2015-11-06 12:26:53

标签: postgresql configuration

我正在为商店目录设计应用程序,并且面临着PostgreSQL的相当慢的性能。

这是简化的db方案(实际上有多对多关系的额外表): db scheme

我希望根据所选目录类别(T恤,包等)按属性(颜色,尺寸,品牌等)实施过滤

以下是为所选类别列表选择可用属性的查询示例。

SELECT DISTINCT T1.attribute_id
FROM item T0 LEFT OUTER JOIN item_attr_color T1 ON ( T0.id = T1.item_id ) 
WHERE T0.catalog_id IN (1, 2, 6, 7, 14, 23, 26, 31, 36, 37, 45, 67, 70, 76, 77, 81, 95, 112, 118, 119, 120, 10, 11, 29, 101, 12, 13, 16, 17, 19, 20, 30, 33, 35, 42, 43, 47, 48, 54, 57, 58, 69, 78, 109, 56, 64, 65, 66, 68, 71, 74, 75, 93, 72, 73, 87, 88, 96, 99, 103, 105, 108, 110);

目前数据库相当小~100k记录,但这个查询仍然需要400毫秒,这是相当多的,因为我有10个不同的过滤器属性,这些查询单独花费4秒,这是不可接受的。

我在所有重要字段上都有索引(btree类型),这里是explain命令的输出

HashAggregate  (cost=28309.30..28309.43 rows=13 width=4) (actual time=343.343..343.347 rows=14 loops=1)
->  Hash Right Join  (cost=24284.42..28074.04 rows=94103 width=4) (actual time=185.278..315.749 rows=115745 loops=1)
     Hash Cond: (t1.item_id = t0.id)
     ->  Seq Scan on core_item_attr_colors t1  (cost=0.00..1797.13 rows=108913 width=8) (actual time=0.006..18.387 rows=107175 loops=1)
     ->  Hash  (cost=23108.13..23108.13 rows=94103 width=4) (actual time=185.182..185.182 rows=93778 loops=1)
           Buckets: 16384  Batches: 1  Memory Usage: 3297kB
           ->  Seq Scan on core_item t0  (cost=0.00..23108.13 rows=94103 width=4) (actual time=0.020..153.334 rows=93778 loops=1)
                 Filter: (catalog_id = ANY ('{1,2,6,7,14,23,26,31,36,37,45,67,70,76,77,81,95,112,118,119,120,10,11,29,101,12,13,16,17,19,20,30,33,35,42,43,47,48,54,57,58,69,78,109,56,64,65,66,68,71,74,75,93,72,73,87,88,96,99,103,105,108,110}'::integer[]))
                 Rows Removed by Filter: 19677
Total runtime: 361.231 ms

正如您所看到的,它并没有使用任何索引,但我注意到减少类别数量最终会强制它使用索引:

 HashAggregate  (cost=18685.04..18685.17 rows=13 width=4) (actual time=166.760..166.764 rows=14 loops=1)
 ->  Hash Right Join  (cost=15515.08..18626.42 rows=23447 width=4) (actual time=56.499..156.865 rows=26501 loops=1)
     Hash Cond: (u2.item_id = u0.id)
     ->  Seq Scan on core_item_attr_colors u2  (cost=0.00..1797.13 rows=108913 width=8) (actual time=0.010..25.706 rows=107175 loops=1)
     ->  Hash  (cost=15221.99..15221.99 rows=23447 width=4) (actual time=56.444..56.444 rows=23099 loops=1)
           Buckets: 4096  Batches: 1  Memory Usage: 813kB
           ->  Bitmap Heap Scan on core_item u0  (cost=1058.03..15221.99 rows=23447 width=4) (actual time=9.732..45.643 rows=23099 loops=1)
                 Recheck Cond: (catalog_id = ANY ('{1,2,6,7,14,23,26,31,36,37,45,67,70,76,77,81,95,112,118,119}'::integer[]))
                 ->  Bitmap Index Scan on core_item_89ed0239  (cost=0.00..1052.17 rows=23447 width=0) (actual time=6.523..6.523 rows=23099 loops=1)
                       Index Cond: (catalog_id = ANY ('{1,2,6,7,14,23,26,31,36,37,45,67,70,76,77,81,95,112,118,119}'::integer[]))
Total runtime: 166.858 ms

我尝试用sqllite替换postgresql,并且对完全相同的数据集上的相同查询得到了相当令人印象深刻的结果,花了不到60毫秒。

这是我的配置文件:

max_connections = 100
temp_buffers = 8MB
work_mem = 96MB
maintenance_work_mem = 512MB
effective_cache_size = 512MB

服务器有6G RAM和SSD磁盘。

我错过了什么?我很欣赏如何提高绩效的任何建议。

UPDATE1: shared_buffers = 1024MB及它的PostgreSQL v.9.3

3 个答案:

答案 0 :(得分:2)

首先,left join是不必要的,除非确实想要获得NULL值(这是值得怀疑的)。所以,你可以写成:

SELECT DISTINCT T1.attribute_id
FROM item T0 JOIN
     item_attr_color T1
     ON T0.id = T1.item_id
WHERE T0.catalog_id IN (1, 2, 6, 7, 14, 23, 26, 31, 36, 37, 45, 67, 70, 76, 77, 81, 95, 112, 118, 119, 120, 10, 11, 29, 101, 12, 13, 16, 17, 19, 20, 30, 33, 35, 42, 43, 47, 48, 54, 57, 58, 69, 78, 109, 56, 64, 65, 66, 68, 71, 74, 75, 93, 72, 73, 87, 88, 96, 99, 103, 105, 108, 110);

接下来,假设您有一个属性表,您可以摆脱distinct并使用子查询:

select a.id
from attribute a
where exists (select 1
              from item_attr_color iac join
                   item i
                   on i.id = iac.item_id
              where i.catalog_id in ( . . .) and
                    iac.attribute_id = a.attribute_id
             );

然后,对于此查询,您需要以下索引:item(id, catalog_id)item_attr_color(attribute_id, item_id),当然还有attribute(id)

这可能有助于提高性能,方法是引入索引并取消distinct的处理。

也可能值得尝试in版本:

select a.id
from attribute a
where a.attribute_id in (select iac.attribute_id
                         from item_attr_color iac join
                              item i
                              on i.id = iac.item_id
                         where i.catalog_id in ( . . .)
                        );

此查询的索引为:item(catalog_id, id)item_attr_color(item_id, attribute_id),当然还有attribute(id)

答案 1 :(得分:0)

索引

很多指数不一定好。理想情况下,你应该有索引:

  • 它们具有高度选择性,即对索引的查询将找到该表的5%或更少;
  • 许多查询都使用它们

每个索引都有插入/更新性能的成本。所以摆脱你不需要的那些。

此外,使用复合索引,部分索引和表达式索引可以获得良好的结果。只是将单个列索引添加到视线中的所有内容很少是最佳选择。

尝试编写受益于仅索引扫描的查询。在这种情况下,我怀疑item_attr_color(item_id, attribute_id)上的索引可能是有益的。

可调参数

如果相对于数据大小和快速磁盘有大量RAM,请降低random_page_cost。许多。尝试

 SET random_page_cost = 1.2

并重新运行您的查询(在同一会话中,之后立即)。

整理

如果您正在排序字符串而不需要本地化排序,则使用COLLATE "C"会非常有帮助。

= ANYIN列出

IN= ANY列表效率不高。它们是顺序遍历的。 100个条目可能不是太糟糕,但如果你真的很多,请考虑加入VALUES列表。

您可能还想查看intarray support for GIN indexes,以便撰写:

WHERE T0.catalog_id && ARRAY[1, 2, 6, 7, 14, 23, 26, 31, 36, 37, 45, 67, 70, 76, 77, 81, 95, 112, 118, 119, 120, 10, 11, 29, 101, 12, 13, 16, 17, 19, 20, 30, 33, 35, 42, 43, 47, 48, 54, 57, 58, 69, 78, 109, 56, 64, 65, 66, 68, 71, 74, 75, 93, 72, 73, 87, 88, 96, 99, 103, 105, 108, 110]

作为可转位操作。 &&表示“重叠”

SQLite的

我对SQLite的表现并不感到惊讶。只读或几乎只读工作负载的简单到中等查询速度非常快。

答案 2 :(得分:0)

除了查询和索引调优之外,如果您怀疑许多查询将由特定谓词驱动(在您的示例中为WHERE T0.catalog_id IN (1, 2, ... 108, 110)),那么请考虑通过索引在表上运行cluster在那一栏上。

这使您更有可能使索引有用,因此被选为执行计划的一部分。