【问题标题】:PostgreSQL ORDER BY taking extremely longPostgreSQL ORDER BY 耗时极长
【发布时间】:2018-03-15 10:27:27
【问题描述】:

我有一个类似这样的查询

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~40000 毫秒,而在实时运行大约需要 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)

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

此外,在应用程序中,用户可以选择一个字母,该字母将返回以该字母开头的所有结果,查询类似于WHERE name LIKE 'a%'。尽管也有数以万计的结果,但指定一个起始字母会大大减少加载时间到大约 1000-2000 毫秒。

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

【问题讨论】:

  • 不相关,但是:WHERE name LIKE '%'可以简化为where name is not null

标签: sql database postgresql performance query-optimization


【解决方案1】:

大排序是问题所在。

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

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

【讨论】:

    【解决方案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
    

    【讨论】:

    • 我只运行了几次这个查询,与之前的 30k+ 相比,每次运行大约 28000 毫秒的结果非常一致,但是,我离我的目标还很远。您还有什么可以提供帮助的建议吗?不过,我感谢您对查询重构的帮助!
    • @M.Account 尝试将您的唯一索引反转为 (name, d_id) - 也删除您的 table__id__idx - 您已经在 id 上有一个 pk 所以它只是重复
    • 除了有用的重构之外,我意识到用于名称列的索引是gist 类型。由于我没有编写代码,所以我自己不太明白这意味着什么,但是在 name 上添加一个额外的 btree 索引也有助于大大加快速度。
    猜你喜欢
    • 1970-01-01
    • 2021-05-28
    • 2014-09-14
    • 1970-01-01
    • 2012-11-04
    • 1970-01-01
    • 1970-01-01
    • 2021-01-10
    • 2013-01-16
    相关资源
    最近更新 更多