PostgreSQL ORDER BY花了很长时间

时间:2017-10-04 05:54:31

标签: sql database postgresql performance query-optimization

我的查询看起来像这样

SELECT DISTINCT
  COALESCE(fa.id, fb.id) AS id,
  COALESCE(fa.d_id, fb.d_id) AS d_id,
  COALESCE(fa.name, fb.name) AS name,
  COALESCE(fa.disabled, fb.disabled) AS disabled,
  COALESCE(fa.deleted, fb.deleted) AS deleted
FROM (
  SELECT * from table WHERE name LIKE '%'
  AND d_id IS NULL AND deleted = false
) fa
FULL JOIN (
  SELECT * from table WHERE name LIKE '%'
  AND d_id = 1 AND deleted = false
) fb ON fa.name = fb.name
ORDER BY name;

其中id是表的主键,name是实际值。 d_id是用户的ID。

基本上,该表有一个巨大的名称列表(大约400k +),如果它没有d_id,则表示它是由系统自动生成的。如果它有d_id,则表示它是用户生成的。

查询应该返回的是整个默认系统名称列表加上某个用户添加的名称(在这种情况下,用户使用d_id为1生成的所有名称)。这就是它为自己执行完全连接的原因。

我的问题是运行查询需要太长时间(在我的本地psql shell上大约30000~40000ms,在live上运行大约15000)。我运行了EXPLAIN ANALYZE并得到了这个

Unique  (cost=8240.78..8272.13 rows=2090 width=42) (actual time=27591.662..28742.062 rows=418018 loops=1)
  ->  Sort  (cost=8240.78..8246.01 rows=2090 width=42) (actual time=27591.659..28504.606 rows=418018 loops=1)
        Sort Key: (COALESCE(table.name, table_1.name)), (COALESCE(table.id, table_1.id)), (COALESCE(table.d_id, table_1.d_id)), (COALESCE(table.disabled, table_1.disabled)), (COALESCE(table.deleted, table_1.deleted))
        Sort Method: external merge  Disk: 13680kB
        ->  Hash Full Join  (cost=8.45..8125.53 rows=2090 width=42) (actual time=11.037..1479.053 rows=418018 loops=1)
              Hash Cond: (table.name = table_1.name)
              ->  Seq Scan on table  (cost=0.00..8109.23 rows=2090 width=27) (actual time=0.048..799.822 rows=418018 loops=1)
                    Filter: ((d_id IS NULL) AND (NOT deleted) AND (name ~~ '%'::citext))
              ->  Hash  (cost=8.44..8.44 rows=1 width=27) (actual time=10.970..10.970 rows=0 loops=1)
                    Buckets: 1024  Batches: 1  Memory Usage: 8kB
                    ->  Index Scan using table__d_id__name__idx on table table_1  (cost=0.42..8.44 rows=1 width=27) (actual time=10.970..10.970 rows=0 loops=1)
                          Index Cond: (d_id = 1)
                          Filter: ((NOT deleted) AND (name ~~ '%'::citext))

虽然我无法完全理解它,但我可以说,为什么花费太长时间的大多数原因都在于排序(ORDER BY)函数。

我的索引如下:

Indexes:
    "table_pkey" PRIMARY KEY, btree (id)
    "table__d_id__name__idx" UNIQUE, btree (d_id, name)
    "table__name__idx" gist (name gist_trgm_ops)
    "table__id__idx" btree (id)

我尝试过使用不同的索引,重构查询并使用代码,但它仍然需要一段时间。我已经尝试删除除主键索引之外的所有索引,并且查询以某种方式加速到~23000ms。

此外,在应用程序中,用户可以选择一个字母,该字母将返回以该字母开头的所有结果,查询看起来像WHERE name LIKE 'a%'。尽管还有成千上万的结果,但指定一个起始字母会大大减少加载时间到1000-2000ms。

我的目标是在5000到10000毫秒之间加载查询。任何帮助或建议将不胜感激!

2 个答案:

答案 0 :(得分:2)

我认为您可以使用or代替full joindistinct on (name)仅选择唯一名称,order by name, d_id在用户名之前选择系统名称。

select distinct on (name)
    id, d_id, name, disabled, deleted
from table
where deleted = false
and (
    d_id is null
    or d_id = 1
)
order by name, d_id

答案 1 :(得分:0)

问题很大。

如果你没有使用DISTINCT,你可以摆脱排序 我看到在你的情况下,行无论如何都是不同的,因为在应用Unique之前和之后有418018行。 仔细考虑是否真的可以在您的情况下发生重复,或者如果您可以取消DISTINCT并以这种方式解决问题。

如果您需要DISTINCT,则至少应为此查询增加work_mem,以便排序可以在内存中发生而不是溢出到磁盘。这将大大提高性能。