Ahm*_*ham 6 sql database postgresql
在 PostgreSQL 14 数据库中,我有一个事务表,当前包含大约 4 亿个元素以及以下查询:
SELECT "transactions"."id"
FROM "transactions"
WHERE ("wallet_id" = $1)
ORDER BY "transactions"."id" DESC
LIMIT 10
Run Code Online (Sandbox Code Playgroud)
这工作正常并且查询速度很快。输出EXPLAIN ANALYZE:
Limit (cost=84991.29..84991.31 rows=10 width=146) (actual time=1.518..1.519 rows=2 loops=1)
-> Sort (cost=84991.29..85056.88 rows=26235 width=146) (actual time=1.517..1.518 rows=2 loops=1)
Sort Key: id DESC
Sort Method: quicksort Memory: 25kB
-> Index Scan using transactions_wallet_id_index on transactions (cost=0.57..84424.36 rows=26235 width=146) (actual time=1.080..1.497 rows=2 loops=1)
Index Cond: (wallet_id = $1)
Planning Time: 0.850 ms
Execution Time: 1.682 ms
Run Code Online (Sandbox Code Playgroud)
如果我向 where 添加第二个钱包 id,则当两个钱包都有少量交易(第一个钱包 2 个,第二个钱包 57 个)时,查询需要几分钟时间:
SELECT "transactions"."id"
FROM "transactions"
WHERE ("wallet_id" = $1 OR "wallet_id" = $2)
ORDER BY "transactions"."id" DESC
LIMIT 10
Run Code Online (Sandbox Code Playgroud)
其分析:
Limit (cost=0.57..117334.73 rows=10 width=146) (actual time=3683.524..944867.724 rows=10 loops=1)
-> Index Scan Backward using transactions_pkey on transactions (cost=0.57..615664040.26 rows=52471 width=146) (actual time=3683.523..944867.715 rows=10 loops=1)
Filter: ((wallet_id = $1) OR (wallet_id = $2))
Rows Removed by Filter: 117937409
Planning Time: 0.810 ms
Execution Time: 944867.797 ms
Run Code Online (Sandbox Code Playgroud)
经过几个小时的调查后,问题似乎来自ORDER BY与 的结合使用LIMIT,实际上,如果我删除两者之一,查询运行得很快。如果至少有一个钱包的交易数量不少,它也运行得很快。id是主键,并且 上有一个索引wallet_id。
这是非常令人失望和沮丧的。这是我第一次使用 Postgres,查询规划器在如此简单的查询上表现如此糟糕,这一事实确实很难理解。
我希望获得一些有关如何在所有情况下加快查询速度的建议。
我现在尝试了几个小时不同的事情,包括跑步VACUUM(以防万一)和ANALYZE在桌子上,但无济于事。
额外的过滤器使 Postgres 切换到不同的查询计划,结果证明效率极低。该效果或多或少是偶然触发的。根本问题是 Postgres 严重低估了该WHERE子句的选择性。它期望它可以按照请求的排序顺序遍历 PK ( ) 上的索引,并很快transactions_pkey找到几行 ( )。LIMIT 10事实证明,过滤器的选择性非常强,Postgres 必须跳过超过 1.18 亿行(!!!)才能找到足够的匹配项(Rows Removed by Filter: 117937409)。如果没有足够的行来满足限制,则在 Postgres 最终放弃之前必须访问所有行。最坏的情况。
该决定是根据错误或误导性的专栏统计数据做出的。如果您可以改进列统计信息,那么问题可能会自行解决。可能就像这样简单ANALYZE transactions;。有多种方法。这是昨天在 dba.SE 上的相关答案,以及更多相关内容(以及更多类似案例的链接):
碰巧我与 讨论了同样的“暴力”黑客ORDER BY id + 0,您找到了答案。
对于您的具体情况,也有不同的方法。为了获得尽可能最佳的性能,请创建一个多列索引(一次):
CREATE INDEX transactions_wallet_id_id_idx ON transactions (wallet_id, id DESC);
Run Code Online (Sandbox Code Playgroud)
也大大提高了简单查询的性能。(即使这已经相当快了。)您将看到没有“排序”步骤的仅索引扫描(或至少是索引扫描)。
然后使用这个查询:
CREATE INDEX transactions_wallet_id_id_idx ON transactions (wallet_id, id DESC);
Run Code Online (Sandbox Code Playgroud)
所有括号都是必需的。
现在第二个查询的成本大约是第一个查询的两倍,即非常快。