【问题标题】:PostgreSQL slow query with limit 1 and order by unneeded where conditionPostgreSQL 慢查询,限制为 1 并按不需要的 where 条件排序
【发布时间】:2020-01-21 02:57:20
【问题描述】:

我有一张表accounts 和索引

accounts {
    id  text
    num_id  bigint
    pid text
    fid text
    created_at  timestamp with time zone
    updated_at  timestamp with time zone
}

CREATE UNIQUE INDEX accounts_pkey ON public.accounts USING btree (id)
CREATE INDEX fid_idx ON public.accounts USING btree (fid)
CREATE INDEX idx_accounts_pid_fid ON public.accounts USING btree (pid, fid)

而且这个查询很慢

explain analyse SELECT * FROM accounts
WHERE pid = 'hd' AND fid = '123'
ORDER BY  id ASC
LIMIT 1;
Limit  (cost=0.56..3173.34 rows=1 width=123) (actual time=49389.351..49389.351 rows=0 loops=1)
  ->  Index Scan using accounts_pkey on accounts  (cost=0.56..5022497.13 rows=1583 width=123) (actual time=49389.350..49389.350 rows=0 loops=1)
        Filter: ((pid = 'hd'::text) AND (fid = '123'::text))
        Rows Removed by Filter: 56821193
Planning time: 0.094 ms
Execution time: 49389.368 ms

根据这个answer,可以通过添加不需要的where条件pidfid来解决

explain analyse SELECT * FROM accounts
WHERE pid = 'hd' AND fid = '123'
ORDER BY  id ASC, pid, fid
LIMIT 1;

但是,它不起作用

Limit  (cost=0.56..3173.37 rows=1 width=123) (actual time=49495.236..49495.236 rows=0 loops=1)
  ->  Index Scan using accounts_pkey on accounts  (cost=0.56..5022556.07 rows=1583 width=123) (actual time=49495.234..49495.234 rows=0 loops=1)
        Filter: ((pid = 'hd'::text) AND (fid = '123'::text))
        Rows Removed by Filter: 56821555
Planning time: 0.096 ms
Execution time: 49495.253 ms

我是不是不见了?

PostgreSQL 版本:9.6.8

【问题讨论】:

  • 只是好奇,SELECT * FROM accounts ORDER BY id LIMIT 1的运行时间是多少?
  • @TimBiegeleisen,SELECT * FROM accounts ORDER BY id LIMIT 1的运行时间是Limit (cost=0.56..0.65 rows=1 width=123) (actual time=0.010..0.010 rows=1 loops=1) -> Index Scan using accounts_pkey on accounts (cost=0.56..4738719.60 rows=56980788 width=123) (actual time=0.010..0.010 rows=1 loops=1) Planning time: 0.078 ms Execution time: 0.027 ms
  • 我在下面尝试了一个答案,希望能部分解释你所看到的。我不知道为什么 Postgres 会选择这个执行计划,但是索引定义的轻微变化可能会解决所有问题。

标签: postgresql performance limit


【解决方案1】:

从您的 cmets 来看,以下查询实际上非常高效:

SELECT *
FROM accounts
ORDER BY id
LIMIT 1;

之所以表现良好,是因为LIMITORDER BY 这一步是Postgres 在SELECT 之前唯一需要做的事情,而accounts_pkey 唯一索引可以在这里轻松扫描。其实Postgres只需要找到最低的id值,然后再参考聚集索引覆盖SELECT *即可。

但是,您问题中的查询有点不同:

SELECT *
FROM accounts
WHERE pid = 'hd' AND fid = '123'
ORDER BY id ASC
LIMIT 1;

在这种情况下,Postgres 选择扫描整个accounts_pkey 索引,从与您的WHERE 子句对应的过滤步骤开始。因为accounts_pkey 只覆盖id 列,所以Postgres 必须回溯到聚集索引来查找pidfid 的值。理想情况下,Postgres 会从最低的 id 值开始,然后沿着索引向下走,直到找到 pidfid 值上的第一个匹配项。无论 Postgres 决定做什么,以下覆盖索引都可以在这里提供帮助:

CREATE INDEX idx_accounts_cover ON public.accounts USING btree (pid, fid, id);

鉴于现在可以使用上述索引轻松删除近 600 万条记录,id 上的剩余 LIMIT/ORDER BY 操作可能更容易接受。而且由于这个索引也涵盖了id,所以 Postgres 只需要在查询的最后一次回溯到聚集索引。

【讨论】:

  • pid, fid 索引上的基数可能极低? (仅仅因为这种情况具有合理的基数并不排除其他具有非常低基数的组合......)
  • @MatBailie 是的,我也在想这个,想看看 OP 的数据样本。
  • @TimBiegeleisen,我确实有机会将这个索引 (pid, fid, id) 添加到生产环境中。但是,我在 beta 环境中对其进行了测试。添加此索引后,解释结果为)-> Sort (cost=8.39..8.39 rows=1 width=119) (actual time=0.028..0.028 rows=0 loops=1)Sort Key: id Sort Method: quicksort Memory: 25kB -> Index Scan using fid_idx on accounts (cost=0.29..8.38 rows=1 width=119) (actual time=0.020..0.020 rows=0 loops=1) Index Cond: (fid = '123'::text) Filter: (pid = 'hd'::text)。使用fid_idx 而不是 idx_accounts_cover
  • @zangw 好的...这里的事情是,最快的 Postgres 可能是沿着id 上的索引向下走,并在找到第一个匹配记录时停止。但是,如果您的数百万条记录中只有很少一部分匹配 pidfid,那么无论使用何种策略,此过程都可能需要一些时间。
猜你喜欢
  • 2014-02-18
  • 2022-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-29
  • 1970-01-01
  • 2018-10-13
  • 1970-01-01
相关资源
最近更新 更多